如何在装饰器模式实现中正确使用 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<...>
并且只是报告 new
ed 内存而不是 delete
d,还没有。
有几种简单的方法可以改变您的 main()
来避免这个问题:
将对象的分配放入一个块中,并在块后报告内存泄漏:
int main() {
{
std::shared_ptr <AbstractCream> ice1( new IceCream());
// ...
}
_CrtDumpMemoryLeaks();
}
将执行实际工作的代码放入一个单独的函数中,然后在 main()
中调用此函数并报告内存泄漏:
int actualMain() {
std::shared_ptr <AbstractCream> ice1( new IceCream());
// ...
}
int main() {
int rc = actualMain();
_CrtDumpMemoryLeaks();
}
报告早期构造的对象的析构函数的内存泄漏,例如 main()
中的第一件事:
struct Reporter { ~Reporter() { _CrtDumpMemoryLeaks(); } };
int main() {
Reporter reporter;
std::shared_ptr <AbstractCream> ice1( new IceCream());
// ...
}
使用这三种方法,std::shared_ptr<...>
会在报告内存泄漏之前被销毁。我很确定所有这些方法都会使内存泄漏消失。我更喜欢使用第三种方法。
也就是说,从性能的角度来看,std::shared_ptr<...>
的传递方式并不理想:每次都会增加引用计数。当它通过多个层传递时,它在调用时会不必要地上升,而在从调用返回时会不必要地下降。也有多种方法可以解决该问题:
简单的方法是将std::shared_ptr<...>
作为常量引用传递:
ChocolateCream(std::shared_ptr<AbstractCream> const& abs)
: DecoratorCream(abs) {
}
可以说引用传递抑制了复制省略。但是,参数构造只能在一个级别上被省略:当将对象传递给另一个函数时,它是一个命名对象,复制省略规则只允许从命名对象中省略副本 return
和 throw
声明。为最内层的构造函数走这条路可能仍然是合理的。即便如此,传递 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,因为代码的目的是在装饰器模式中尝试智能指针。
我在以下代码中遇到内存泄漏问题。我知道有一些流程。但不确定。如何在这些场景中使用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<...>
并且只是报告 new
ed 内存而不是 delete
d,还没有。
有几种简单的方法可以改变您的 main()
来避免这个问题:
将对象的分配放入一个块中,并在块后报告内存泄漏:
int main() { { std::shared_ptr <AbstractCream> ice1( new IceCream()); // ... } _CrtDumpMemoryLeaks(); }
将执行实际工作的代码放入一个单独的函数中,然后在
main()
中调用此函数并报告内存泄漏:int actualMain() { std::shared_ptr <AbstractCream> ice1( new IceCream()); // ... } int main() { int rc = actualMain(); _CrtDumpMemoryLeaks(); }
报告早期构造的对象的析构函数的内存泄漏,例如
main()
中的第一件事:struct Reporter { ~Reporter() { _CrtDumpMemoryLeaks(); } }; int main() { Reporter reporter; std::shared_ptr <AbstractCream> ice1( new IceCream()); // ... }
使用这三种方法,std::shared_ptr<...>
会在报告内存泄漏之前被销毁。我很确定所有这些方法都会使内存泄漏消失。我更喜欢使用第三种方法。
也就是说,从性能的角度来看,std::shared_ptr<...>
的传递方式并不理想:每次都会增加引用计数。当它通过多个层传递时,它在调用时会不必要地上升,而在从调用返回时会不必要地下降。也有多种方法可以解决该问题:
简单的方法是将
std::shared_ptr<...>
作为常量引用传递:ChocolateCream(std::shared_ptr<AbstractCream> const& abs) : DecoratorCream(abs) { }
可以说引用传递抑制了复制省略。但是,参数构造只能在一个级别上被省略:当将对象传递给另一个函数时,它是一个命名对象,复制省略规则只允许从命名对象中省略副本
return
和throw
声明。为最内层的构造函数走这条路可能仍然是合理的。即便如此,传递std::shared_ptr<...>
时也应该移动它(在这种情况下传递给成员变量的构造):DecoratorCream(std::shared_ptr<AbstractCream> abs) : AbCream(std::move(abs)) { }
如果你也想在其他构造函数中按值传递参数,你至少应该
std::move(...)
参数。这样做应该避免引用计数,但它仍然不会避免所有工作,因为它需要在每个级别上 construct/destroy astd::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,因为代码的目的是在装饰器模式中尝试智能指针。