C++ 使用Init()进行内存分配比使用构造函数更好吗?
假设我有这样的东西:C++ 使用Init()进行内存分配比使用构造函数更好吗?,c++,exception,memory-management,constructor,C++,Exception,Memory Management,Constructor,假设我有这样的东西: class obj001 { public: obj001() { std::cout << "ctor == obj001" << std::endl; } ~obj001() { std::cout << "dtor == obj001" << std::endl; } }; class obj002 { public: obj002() {
class obj001
{
public:
obj001() {
std::cout << "ctor == obj001" << std::endl;
}
~obj001() {
std::cout << "dtor == obj001" << std::endl;
}
};
class obj002
{
public:
obj002() {
std::cout << "ctor == obj002" << std::endl;
}
~obj002() {
std::cout << "dtor == obj002" << std::endl;
}
};
class packet001
{
public:
packet001(): p01(NULL), p02(NULL) {
/*p01 = new obj001;
p02 = new obj002;
throw "hahaha";*/
std::cout << "CTOR == PACKET01" << std::endl;
}
~packet001() {
delete p01;
delete p02;
std::cout << "DTOR == PACKET01" << std::endl;
}
void init() {
p01 = new obj001;
p02 = new obj002;
throw "hahaha";
}
obj001* p01;
obj002* p02;
};
然后init()
失败,将调用superpack
的Dtor
但是如果我将内存分配放在超级包的Ctor中,
(当然,不要执行init()
)
然后在Ctor失败后,将不会调用Dtor,因此p01
和p02
会泄漏
因此,最好使用init()
谢谢 最好的办法是完全避免此类分配。对于许多事情,可以将实例直接放在类中。如果确实需要指针,可以使用unique_ptr和shared_ptr进行自动内存管理
在您的示例中,这很好:
struct packet001
{
obj001 p01;
obj002 p02;
};
如果需要它们作为指针:
struct packet001
{
packet001()
: p01(new obj001),
p02(new obj002)
{
}
std::unique_ptr<obj001> p01;
std::unique_ptr<obj002> p02;
};
struct packet001
{
packet001()
:p01(新obj001),
p02(新obj002)
{
}
std::唯一的ptr p01;
std::唯一的ptr p02;
};
析构函数中的内存将自动释放,如果在构造过程中发生异常,则释放将正常进行。最好的办法是完全避免此类分配。对于许多事情,可以将实例直接放在类中。如果确实需要指针,可以使用unique_ptr和shared_ptr进行自动内存管理
在您的示例中,这很好:
struct packet001
{
obj001 p01;
obj002 p02;
};
如果需要它们作为指针:
struct packet001
{
packet001()
: p01(new obj001),
p02(new obj002)
{
}
std::unique_ptr<obj001> p01;
std::unique_ptr<obj002> p02;
};
struct packet001
{
packet001()
:p01(新obj001),
p02(新obj002)
{
}
std::唯一的ptr p01;
std::唯一的ptr p02;
};
内存将在析构函数中自动释放,如果在构造过程中发生异常,则释放将正确发生。您是否应该捕获Ctor中的所有异常,并在异常到达Ctor内部时正确清理?您是否应该捕获Ctor中的所有异常,并在异常到达Ctor内部时正确清理?使用两阶段构造,普通构造之后是对init
函数的外部调用,这意味着构造之后您还不知道手头是否有有效的对象。这就意味着,在任何函数中,只要有一个对象作为参数,你就不知道这个对象是否有效。这意味着大量额外的检查和不确定性,这反过来意味着bug和额外的工作,因此,构造函数应该建立一个功能齐全的有效对象
进入“功能,有效”概念的一组假设称为类不变量
换句话说,更学术的措辞,构造器的工作是建立类不变量,这样在构造之后它就保持不变了
然后在每个外部可用的操作中保持对象有效,意味着它将继续被保证有效。因此,不需要进一步的有效性检查。该方案并非完全适用于所有对象(反例是一个表示文件的对象,其中任何操作都可能导致该对象实际上无效),但大多数情况下,它是一个好主意,效果很好,如果它不能直接工作,则适用于部分
因此,在构造函数中,您应该通过以下方式之一确保清理:
- 使用标准库容器(或第三方容器),而不是直接处理原始数组和动态分配
- 或者使用每个仅管理一个资源的子对象。子对象可以是数据成员或基类。如果是数据成员,则它可以是智能指针
- 或者在最坏的情况下,使用
try
-catch
进行直接清理
在技术上也可以使用检查返回值的C思想在必要时调用直接清理。但是上面的列表是为了减少易用性和安全性。C风格的编码在该列表的底部之外
<> P> C++语言创作者Bjarne Stroustrup在他的附录中写了一点关于这个主题的文章。只需下载PDF,然后在PDF阅读器中搜索“init”(“。您应该直接进入§E3.5节,关于构造函数和不变量;请至少阅读§E.3.5.1节,关于使用init()函数
正如比亚恩在那里列出的那样
[…]拥有一个单独的init()
函数是实现
[1] 忘记调用init()
(§10.2.3),
[2] 忘记测试init()
,
[3] 多次调用init()
,
[4] 忘记init()
可能引发异常,然后
[5] 在调用init()
之前使用该对象
我认为,比亚恩的讨论对初学者来说是很好的,整本书也是如此
<>但是,注意到两个构造的共同原因,即支持<强C++派生类初始化< <强> >,根本没有提到,这里不是Bjarne的图片的一部分。这是许多GUI框架中两阶段初始化的原因。然而,XIST证明,大多数席上都是一个教育问题——那些早期的C++程序员根本不知道,或者不能假设他们的图书馆用户会理解,C++ +RAII.< /P> < P>使用<强>两阶段构造>普通构造,接着调用一个<代码> init < /Cuff>函数,即THA。在构造之后,你还不知道手头是否有一个有效的对象。这意味着在任何以参数形式获取对象的函数中,你都不知道该对象是否有效。这意味着大量额外的检查和不确定性,这反过来意味着bug和额外的工作,因此,构造函数应该建立一个完整的函数nal,有效对象
那个