And*_*ong 72 c++ oop constructor destructor class
假设我们有一个(玩具)C++类,如下所示:
class Foo {
public:
Foo();
private:
int t;
};
Run Code Online (Sandbox Code Playgroud)
由于没有定义析构函数,因此C++编译器应该自动为类创建一个析构函数Foo
.如果析构函数不需要清理任何动态分配的内存(也就是说,我们可以合理地依赖编译器给我们的析构函数),那么将定义一个空的析构函数,即.
Foo::~Foo() { }
Run Code Online (Sandbox Code Playgroud)
做与编译器生成的一样的事情?那个空构造函数怎么样 - 也就是说,Foo::Foo() { }
?
如果存在差异,它们存在于何处?如果没有,一种方法优于另一种方法吗?
Joh*_*itb 116
它会做同样的事情(实质上没什么).但是,如果你没有写它,那就不一样了.因为编写析构函数需要一个可操作的基类析构函数.如果基类析构函数是私有的,或者如果有任何其他原因无法调用它,那么您的程序就会出错.考虑一下
struct A { private: ~A(); };
struct B : A { };
Run Code Online (Sandbox Code Playgroud)
没关系,只要您不需要破坏类型B的对象(因此,隐式地使用类型A) - 就像您从未在动态创建的对象上调用delete,或者您从未在其中创建对象第一名.如果这样做,编译器将显示适当的诊断.现在,如果你明确提供一个
struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } };
Run Code Online (Sandbox Code Playgroud)
那个将尝试隐式调用基类的析构函数,并将在定义时间引起诊断~B
.
另一个区别在于析构函数的定义和对成员析构函数的隐式调用.考虑这个智能指针成员
struct C;
struct A {
auto_ptr<C> a;
A();
};
Run Code Online (Sandbox Code Playgroud)
假设类型的对象C
是在.cpp
文件中A的构造函数的定义中创建的,该构造函数还包含struct的定义C
.现在,如果使用struct A
,并且需要销毁A
对象,编译器将提供析构函数的隐式定义,就像上面的情况一样.该析构函数还将隐式调用auto_ptr对象的析构函数.这将删除它所持有的指针,指向C
对象 - 不知道定义C
!这出现在.cpp
定义struct A的构造函数的文件中.
这实际上是实现pimpl习语的常见问题.这里的解决方案是添加析构函数并在.cpp
文件中提供它的空定义,其中C
定义了结构.在它调用其成员的析构函数时,它将知道struct的定义C
,并且可以正确地调用它的析构函数.
struct C;
struct A {
auto_ptr<C> a;
A();
~A(); // defined as ~A() { } in .cpp file, too
};
Run Code Online (Sandbox Code Playgroud)
请注意,boost::shared_ptr
没有这个问题:当以某种方式调用其构造函数时,它需要一个完整的类型.
在当前C++中有所不同的另一点是,当你想memset
在这样一个用户声明了析构函数的对象上使用和朋友时.这些类型不再是POD(普通旧数据),并且不允许进行位复制.请注意,实际上并不需要这种限制 - 并且下一个C++版本改进了这种情况,因此只要不进行其他更重要的更改,它就允许您仍然对这些类型进行位复制.
既然你问过构造函数:嗯,对于这些,同样的事情也是如此.请注意,构造函数还包含对析构函数的隐式调用.在诸如auto_ptr之类的东西上,这些调用(即使实际上并没有在运行时完成 - 纯粹的可能性在这里已经很重要)会对析构函数造成同样的伤害,并且当构造函数中的某些内容抛出时会发生 - 然后编译器需要调用析构函数的成员.这个答案使用了默认构造函数的隐式定义.
此外,我所说的关于上面的析构函数的可见性和POD也是如此.
初始化有一个重要的区别.如果你把一个用户声明的构造函数,你的类型不再接收成员的值初始化,并且由你的构造函数来完成所需的任何初始化.例:
struct A {
int a;
};
struct B {
int b;
B() { }
};
Run Code Online (Sandbox Code Playgroud)
在这种情况下,以下内容始终为真
assert(A().a == 0);
Run Code Online (Sandbox Code Playgroud)
虽然以下是未定义的行为,因为b
从未初始化(您的构造函数省略了).该值可能为零,但也可能是任何其他奇怪的值.尝试从这样的未初始化对象中读取会导致未定义的行为.
assert(B().b == 0);
Run Code Online (Sandbox Code Playgroud)
这也是在使用此语法真new
像new A()
(注意最后括号-如果他们被省略值初始化没有这样做,而且因为没有用户声明的构造,可以将其初始化,a
将未初始化).
Gre*_*osz 18
我知道我在讨论中已经迟到了,但是我的经验表明,与编译器生成的析构函数相比,面对空的析构函数时编译器的行为会有所不同.至少在MSVC++ 8.0(2005)和MSVC++ 9.0(2008)中就是这种情况.
在查看生成的程序集以获取一些使用表达式模板的代码时,我意识到在发布模式下,对my的调用BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
从未被内联.(请不要注意确切的类型和操作员签名).
为了进一步诊断问题,我启用了默认关闭的各种编译器警告.该C4714警告是特别有趣.当标记的函数__forceinline
没有内联时,编译器会发出它.
我启用了C4714警告并且我标记了操作员,__forceinline
我可以验证编译器报告它无法内联对操作员的调用.
在文档中描述的原因中,编译器无法内联标记__forceinline
为for 的函数:
当-GX/EHs/EHa打开时,函数按值返回可解除对象的函数
这就是我的情况BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
.BinaryVectorExpression
由值返回,即使析构函数为空,也会将此返回值视为不可解除的对象.添加throw ()
到析构函数并没有帮助编译器,我仍然避免使用异常规范.注释掉空的析构函数让编译器完全内联代码.
外卖是从现在开始,在每个课程中,我都写了注释掉空的析构函数,让人们知道析构函数不会故意做任何事情,就像人们注释掉空的异常规范`/*throw()*/一样表明析构函数不能抛出.
//~Foo() /* throw() */ {}
Run Code Online (Sandbox Code Playgroud)
希望有所帮助.
Fai*_*ali 12
您在类之外定义的空析构函数在大多数情况下具有类似的语义,但不是全部.
具体来说,隐式定义的析构函数
1)是一个内联的公共成员(你的内联不是内联的)
2)被表示为一个简单的析构函数(必须创建可以在联合中的普通类型,你的不能)
3)有一个异常规范(throw) (),你的没有)