【问题标题】:How to use shared_ptr in a decorator pattern implementation correctly?如何在装饰器模式实现中正确使用 shared_ptr?
【发布时间】:2016-02-24 10:32:30
【问题描述】:

我在以下代码中遇到内存泄漏问题。我知道有一些流量。但不确定。如何在这些场景中使用 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::make_shared 并考虑使用std::unique_ptr
  • 您能否详细说明以上内容?我的理解是 unique_ptr 如果我们有一个数组来清除相似或删除[];

标签: c++ design-patterns memory-leaks decorator shared-ptr


【解决方案1】:
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;
}

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

【讨论】:

    【解决方案2】:

    问题似乎不在于您的类中使用std::shared_ptr&lt;...&gt;:这在语义上似乎是正确的(不过,要详细查看代码太多了)。相反,我认为您的main() 是错误的:您尝试在对象还活着的时间点确定内存泄漏。我不是 Windows 程序,但我很确定 _CrtDumpMemoryLeak() 不知道 std::shared_ptr&lt;...&gt;,只是报告了 newed 内存,但它还不是 deleted。

    有几种简单的方法可以更改您的main() 以避免该问题:

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

      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&lt;...&gt;s 在报告内存泄漏之前被销毁。我很确定所有这些方法都会使内存泄漏消失。我的偏好是使用第三种方法。

    也就是说,从性能的角度来看,std::shared_ptr&lt;...&gt; 的传递方式并不理想:每次都会增加引用计数。当它通过多个层时,它在调用时不必要地向上颠簸,在从调用返回时被颠倒。也有多种方法可以解决这个问题:

    1. 简单的方法是将std::shared_ptr&lt;...&gt; 作为常量引用传递:

      ChocolateCream(std::shared_ptr<AbstractCream> const& abs)
          : DecoratorCream(abs) {
      }
      
    2. 可以说通过引用传递抑制了复制省略。但是,参数构造只能在一个级别上省略:当将对象传递给另一个函数时,它是一个命名对象,并且复制省略的规则仅允许在 returnthrow 语句中从命名对象中删除副本。为最里面的构造函数走这条路线可能仍然是合理的。即使这样,std::shared_ptr&lt;...&gt; 在传递它时也应该移动(在这种情况下,传递给成员变量的构造):

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

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

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

    【讨论】:

    • @Dietman:非常感谢您的帮助。这解决了内存检查。它实际上是清除内存,但是正如你所说的检查点是错误的。 std::endl 的第二点。在换行之后打印有什么更好的选择?最后但同样重要的是,我仍然无法将嵌套的新指针传递给外部共享指针。我需要先用另一个共享指针创建一个,然后必须将它传递给另一个以获得更多数量的包装(在装饰器的情况下)。这是预期的吗?
    • /*下面的代码示例展示了我如何尝试三层装饰*/std::shared_ptr ice2(new IceCream()); std::shared_ptr choco1(new ChocolateCream(ice2)); std::shared_ptr straw1(new StrawberryCream(choco1));
    • @Anand:回复std::endl:只需使用'\n'。如果您按照答案中的link 进行操作,您会发现推理和替代方案的合理详尽的记录。需要的对象:我不是特别擅长面向对象的编程,但我认为你会期望你有被装饰的对象。
    • @Dietman:谢谢。现在我有理由避免使用 std::endl。
    猜你喜欢
    • 2020-02-29
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 2012-02-16
    • 1970-01-01
    • 2011-03-08
    • 2019-03-25
    • 1970-01-01
    相关资源
    最近更新 更多