一个“空的”构造函数或析构函数是否和生成的函数一样?
假设我们有一个(玩具)C ++类,如下所示:
class Foo { public: Foo(); private: int t; };
由于没有定义析构函数,因此C ++编译器应该为Foo类自动创build一个。 如果析构函数不需要清理任何dynamic分配的内存(也就是说,我们可以合理地依赖编译器给我们的析构函数),将会定义一个空的析构函数。
Foo::~Foo() { }
做与编译器生成的一样的东西? 什么是空的构造函数 – 也就是Foo::Foo() { }
?
如果有差异,它们在哪里存在? 如果没有,是一种比另一种更受欢迎的方法?
它会做同样的事情(本质上没有)。 但是,如果你没有写出来就不一样了。 因为编写析构函数将需要一个工作的基类parsing器。 如果基类析构函数是私有的,或者如果有任何其他的原因它不能被调用,那么你的程序是错误的。 考虑这个
struct A { private: ~A(); }; struct B : A { };
这是可以的,只要你不需要破坏typesB的对象(因此隐含typesA) – 就像你永远不会在dynamic创build的对象上调用delete一样,或者你永远不会创build它的对象第一个地方。 如果这样做,那么编译器将显示一个适当的诊断。 现在,如果你明确提供一个
struct A { private: ~A(); }; struct B : A { ~B() { /* ... */ } };
那个会试图隐式地调用基类的析构函数,并且会在〜B的定义时间已经导致一个诊断。
还有另外一个区别在于析构函数的定义和隐含的成员析构函数的调用。 考虑这个智能指针成员
struct C; struct A { auto_ptr<C> a; A(); };
我们假设typesC
的对象是在.cpp
文件的A的构造函数的定义中创build的,它也包含了struct C
的定义。 现在,如果你使用struct A
,并且需要销毁一个A
对象,那么编译器将提供析构函数的隐式定义,就像上面的情况一样。 那个析构函数也会隐式地调用auto_ptr对象的析构函数。 这将删除指针,它指向C
对象 – 不知道C
的定义! 那出现在.cpp
文件中的结构A的构造函数被定义。
这实际上是实施pimpl习语的一个常见问题。 这里的解决scheme是添加一个析构函数,并在定义了struct C
的.cpp
文件中提供一个空的定义。 当它调用其成员的析构函数时,它就会知道struct C
的定义,并可以正确地调用它的析构函数。
struct C; struct A { auto_ptr<C> a; A(); ~A(); // defined as ~A() { } in .cpp file, too };
请注意, boost::shared_ptr
没有这个问题:它需要一个完整的types,当它的构造函数以某种方式被调用。
当前C ++有所不同的另一点是,当你想在这样一个拥有用户声明的析构函数的对象上使用memset
和friends。 这些types不再是POD(普通的旧数据),并且不允许被复制。 请注意,这个限制并不是真的需要 – 下一个C ++版本已经改善了这种情况,所以只要还没有做其他更重要的更改,它仍然可以对这种types进行位复制。
既然你问build设者:那么,这些事情都是一样的。 请注意,构造函数还包含对析构函数的隐式调用。 在像auto_ptr这样的东西中,这些调用(即使在运行时没有实际完成 – 纯粹的可能性在这里已经很重要)将会造成与析构函数相同的危害,并且当构造函数中的某些内容抛出时 – 编译器需要调用析构函数的成员。 这个答案使一些默认的构造函数的隐式定义的使用。
另外,关于上面析构函数的可见性和POD也是如此。
初始化有一个重要的区别。 如果你把一个用户声明的构造函数,你的types不会收到成员的值初始化,并且由你的构造函数来做任何需要的初始化。 例:
struct A { int a; }; struct B { int b; B() { } };
在这种情况下,总是如此
assert(A().a == 0);
虽然以下是未定义的行为,因为b
从未初始化(您的构造函数省略)。 价值可能是零,但也可能是其他奇怪的价值。 尝试从这样一个未初始化的对象读取会导致未定义的行为。
assert(B().b == 0);
这也适用于new
使用这种语法,如new A()
(注意括号在最后 – 如果省略值初始化没有完成,并且由于没有用户声明的构造函数可以初始化它, a
将是保持未初始化)。
我知道我迟到了,但是我的经验告诉我们,编译器在面对一个空的析构函数时的行为与编译器生成的行为不同。 至less这是MSVC ++ 8.0(2005)和MSVC ++ 9.0(2008)的情况。
在使用expression式模板查看生成的程序集代码时,我意识到在释放模式下,对BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
的调用从未内联过。 (请不要注意确切的types和操作员签名)。
为了进一步诊断问题,我启用了默认closures的各种编译器警告 。 C4714警告特别有趣。 当用__forceinline
标记的__forceinline
没有被内联时,它由编译器发出。
我启用了C4714警告,并用__forceinline
标记了运算符,我可以validation编译器报告无法将调用内联到运算符。
在文档中描述的原因之一中,编译器无法为使用__forceinline
标记的__forceinline
内联:
当-GX / EHs / EHa打开时,通过值返回一个可放置对象的函数
这是我的BinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)
。 BinaryVectorExpression
是由值返回,即使它的析构函数是空的,它使这个返回值被视为一个unwindable对象。 向析构函数添加throw ()
并不能帮助编译器,而且我也避免使用exception规范 。 注释掉空析构函数让编译器完全内联代码。
从现在开始,在每一个类中,我写了空的析构函数,让人们知道析构函数什么也不做,就像人们用空的exception说明`/ * throw()* /表明析构函数不能抛出。
//~Foo() /* throw() */ {}
希望有所帮助。
在类之外定义的空析构函数在大多数方面都具有相似的语义,但并不是全部。
具体来说就是隐式定义的析构函数
1)是一个内联公共成员(你的内联)
2)被表示为一个微不足道的析构函数(必要的是可以在工会中进行微不足道的types,你不能)
3)有一个exception说明(throw(),你的不)
是的,那个空的析构函数和自动生成的相同。 我总是让编译器自动生成它们; 我不认为有必要明确地指定析构函数,除非你需要做一些不寻常的事情:使之成为虚拟的或私有的。
我同意大卫,除了我会说,定义一个虚拟析构函数通常是一个好习惯
virtual ~Foo() { }
错过虚拟析构函数会导致内存泄漏,因为从你的Foo类inheritance的人可能没有注意到它们的析构函数永远不会被调用!
我最好把空的声明,它告诉任何未来的维护者,这不是一个疏忽,你真的有意使用默认的。
由于可以引用定义,因此空的定义是正确的
virtual ~GameManager() { };
空的声明在外观上看似相似
虚拟〜GameManager();
但仍然邀请虚拟析构函数错误的可怕的没有定义
Undefined symbols: "vtable for GameManager", referenced from: __ZTV11GameManager$non_lazy_ptr in GameManager.o __ZTV11GameManager$non_lazy_ptr in Main.o ld: symbol(s) not found