如何在装饰器模式实现中正确使用 shared_ptr?

How to use shared_ptr in a decorator pattern implementation correctly?

我在以下代码中遇到内存泄漏问题。我知道有一些流程。但不确定。如何在这些场景中使用shared_ptr?如果我需要添加更多装饰器,比如 Chocolate-Pista-Icecream,如何正确传递指针以便它在出口处被删除?

    class AbstractCream
{
public:
    virtual void ShowFlavour() = 0;
    virtual ~AbstractCream()
    {
        cout << endl << "AbstractCream-DTOR";
    }
};

class IceCream :public AbstractCream
{
public:
    void ShowFlavour()
    {
        cout << "IceCream";
    }
    ~IceCream()
    {
        cout << endl << "IceCream Dtor";
    }
};

class DecoratorCream :public AbstractCream
{
private:
    std::shared_ptr<AbstractCream> AbCream;
public:
    DecoratorCream(std::shared_ptr<AbstractCream>abs) :AbCream(abs)
    {}
    void ShowFlavour()
    {
        AbCream->ShowFlavour();
    }
    virtual ~DecoratorCream()
    {
        cout << endl << "DecoratorCream-DTOR";

    }
};

class ChocolateCream : public DecoratorCream
{
public:
    ChocolateCream(std::shared_ptr<AbstractCream>abs) :DecoratorCream(abs)
    {}
    void ShowFlavour()
    {
        cout << "CholocateCream added..";
        DecoratorCream::ShowFlavour();
    }
    ~ChocolateCream()
    {
        cout << endl << "ChocolateCream-DTOR";
    }

};
class PistaCream : public DecoratorCream
{
public:
    PistaCream(std::shared_ptr<AbstractCream> abs) :DecoratorCream(abs)
    {}
    void ShowFlavour()
    {
        cout << "PistaCream added..";
        DecoratorCream::ShowFlavour();
    }
    ~PistaCream()
    {
        cout << endl << "PistaCream-DTOR";
    }
};

class StrawberryCream : public DecoratorCream
{
public:
    StrawberryCream(std::shared_ptr<AbstractCream> abs) :DecoratorCream(abs)
    {}
    void ShowFlavour()
    {
        cout << "StrawberryCream added..";
        DecoratorCream::ShowFlavour();
    }
    ~StrawberryCream()
    {
        cout << endl << "StrawberryCream-DTOR";
    }
};


int main()
{
    std::shared_ptr <AbstractCream> ice1( new IceCream());
    std::shared_ptr <PistaCream> pista1(new PistaCream(ice1));
    std::shared_ptr <AbstractCream> ice2(new IceCream());
    std::shared_ptr <ChocolateCream>choco1( new ChocolateCream(ice2));

    pista1->ShowFlavour();
    cout << endl;
    choco1->ShowFlavour();
    cout << endl;

    getchar();
    _CrtDumpMemoryLeaks();
    return 0;
}

问题似乎不是您 类 中的 std::shared_ptr<...> 用法:这在语义上似乎是正确的(不过代码太多,无法详细查看)。相反,我认为您的 main() 是错误的:您尝试在对象仍然存在的时间点确定内存泄漏。我不是 Windows 程序,但我很确定 _CrtDumpMemoryLeak() 不知道 std::shared_ptr<...> 并且只是报告 newed 内存而不是 deleted,还没有。

有几种简单的方法可以改变您的 main() 来避免这个问题:

  1. 将对象的分配放入一个块中,并在块后报告内存泄漏:

    int main() {
        {
            std::shared_ptr <AbstractCream> ice1( new IceCream());
            // ...
        }
        _CrtDumpMemoryLeaks();
    }
    
  2. 将执行实际工作的代码放入一个单独的函数中,然后在 main() 中调用此函数并报告内存泄漏:

    int actualMain() {
        std::shared_ptr <AbstractCream> ice1( new IceCream());
        // ...
    }
    int main() {
        int rc = actualMain();
        _CrtDumpMemoryLeaks();
    }
    
  3. 报告早期构造的对象的析构函数的内存泄漏,例如 main() 中的第一件事:

    struct Reporter { ~Reporter() { _CrtDumpMemoryLeaks(); } };
    int main() {
        Reporter reporter;
        std::shared_ptr <AbstractCream> ice1( new IceCream());
        // ...
    }
    

使用这三种方法,std::shared_ptr<...> 会在报告内存泄漏之前被销毁。我很确定所有这些方法都会使内存泄漏消失。我更喜欢使用第三种方法。

也就是说,从性能的角度来看,std::shared_ptr<...> 的传递方式并不理想:每次都会增加引用计数。当它通过多个层传递时,它在调用时会不必要地上升,而在从调用返回时会不必要地下降。也有多种方法可以解决该问题:

  1. 简单的方法是将std::shared_ptr<...>作为常量引用传递:

    ChocolateCream(std::shared_ptr<AbstractCream> const& abs)
        : DecoratorCream(abs) {
    }
    
  2. 可以说引用传递抑制了复制省略。但是,参数构造只能在一个级别上被省略:当将对象传递给另一个函数时,它是一个命名对象,复制省略规则只允许从命名对象中省略副本 returnthrow 声明。为最内层的构造函数走这条路可能仍然是合理的。即便如此,传递 std::shared_ptr<...> 时也应该移动它(在这种情况下传递给成员变量的构造):

    DecoratorCream(std::shared_ptr<AbstractCream> abs)
        : AbCream(std::move(abs)) {
    }
    

    如果你也想在其他构造函数中按值传递参数,你至少应该 std::move(...) 参数。这样做应该避免引用计数,但它仍然不会避免所有工作,因为它需要在每个级别上 construct/destroy a std::shared_ptr<...>。但是,至少,可以避免引用计数的同步维护。

因为我提到了一个性能问题:stop using std::endl。这对你没有多大好处。在您的使用中,它只会减慢程序速度。

class AbstractCream
{
public:
    virtual void ShowFlavour() = 0;
    virtual ~AbstractCream()
    {
        cout << endl << "AbstractCream-DTOR";
    }
};

class IceCream :public AbstractCream
{
public:
    void ShowFlavour()
    {
        cout << "IceCream";
    }
    ~IceCream()
    {
        cout << endl << "IceCream Dtor";
    }
};

class DecoratorCream :public AbstractCream
{
private:
    std::shared_ptr<AbstractCream> AbCream;
public:
    DecoratorCream(const std::shared_ptr<AbstractCream> &abs) :AbCream(abs)
    {}
    void ShowFlavour()
    {
        AbCream->ShowFlavour();
    }
    virtual ~DecoratorCream()
    {
        cout << endl << "DecoratorCream-DTOR";

    }
};

class ChocolateCream : public DecoratorCream
{
public:
    ChocolateCream(const std::shared_ptr<AbstractCream>& abs) :DecoratorCream(abs)
    {}
    void ShowFlavour()
    {
        cout << "CholocateCream added..";
        DecoratorCream::ShowFlavour();
    }
    ~ChocolateCream()
    {
        cout << endl << "ChocolateCream-DTOR";
    }

};
class PistaCream : public DecoratorCream
{
public:
    PistaCream(const std::shared_ptr<AbstractCream> &abs) :DecoratorCream(abs)
    {}
    void ShowFlavour()
    {
        cout << "PistaCream added..";
        DecoratorCream::ShowFlavour();
    }
    ~PistaCream()
    {
        cout << endl << "PistaCream-DTOR";
    }
};

class StrawberryCream : public DecoratorCream
{
public:
    StrawberryCream(const std::shared_ptr<AbstractCream>& abs) :DecoratorCream(abs)
    {}
    void ShowFlavour()
    {
        cout << "StrawberryCream added..";
        DecoratorCream::ShowFlavour();
    }
    ~StrawberryCream()
    {
        cout << endl << "StrawberryCream-DTOR";
    }
};


//-------------------dec--------------------------------------------------------------//
struct DummyToLeakCheck
{
public:
    ~DummyToLeakCheck()
    {
        _CrtDumpMemoryLeaks();
    }
};
int main()
{
    DummyToLeakCheck myLeakChecker;
    std::shared_ptr <AbstractCream> ice1( new IceCream());
    std::shared_ptr <PistaCream> pista1(new PistaCream(ice1));
    std::shared_ptr <AbstractCream> ice2(new IceCream());
    std::shared_ptr <ChocolateCream>choco1( new ChocolateCream(ice2));
    std::shared_ptr <StrawberryCream>straw1(new StrawberryCream(choco1));

    pista1->ShowFlavour();
    cout << endl;
    choco1->ShowFlavour();
    cout << endl;
    straw1->ShowFlavour();
    cout << endl;

    getchar();

    return 0;
}

使用第一个答案中提到的泄漏检查器有助于更正原始 code.Modified 代码。现在忽略 std::endl,因为代码的目的是在装饰器模式中尝试智能指针。