迄今为止,我一直在设计我的类,假设每个构造函数调用最多有一个析构函数调用 [...]
你仍然可以“假设”,因为它是真的。每个构造函数调用都将与一个析构函数调用同时进行。(请记住,如果您自己处理空闲/堆内存中的内容。)
[..] 并且可以依赖于编译器 [...]
在这种情况下,它不能。它依赖于优化。如果应用优化,MSVC 和 GCC 的行为相同。
为什么你看不到相同的行为?
1. 你不会跟踪你的对象发生的所有事情。编译器生成的函数会绕过您的输出。
如果你想“跟进”你的编译器对你的对象所做的事情,你应该定义所有的特殊成员,这样你就可以真正跟踪所有内容并且不会被任何隐式函数绕过。
struct My
{
My() { cout << "My constructor " << endl; }
My(My const&) { cout << "My copy-constructor " << endl; }
My(My &&) { cout << "My move-constructor " << endl; }
My& operator=(My const&) { cout << "My copy-assignment " << endl; }
My& operator=(My &&) { cout << "My move-assignment " << endl; }
~My() { cout << "My destructor " << endl; }
};
[注意:如果你有副本,移动构造函数和移动赋值将不会隐式出现,但很高兴看到编译器何时使用它们中的哪一个。]
2. 你没有在 MSVC 和 GCC 上进行优化编译。
如果使用 MSVC++11/O2
选项编译,则输出为:
我的构造
函数 我的函数
我的析构函数
如果在调试模式下编译/没有优化:
我的构造
函数 我的函数
我的移动构造函数 我的
析构函数
我的析构函数
我无法对 gcc 进行测试以验证是否有一个选项可以强制执行所有这些步骤,但-O0
我猜应该可以做到这一点。
这里优化和非优化编译有什么区别?
没有任何复制省略的情况:
此行中完全“未优化”的行为My my_in_main = function();
(更改名称以使事情清楚)将是:
- 称呼
function()
- 在函数构造 My
My my;
- 输出东西。
- 复制构造
my
到返回值实例中。
- 返回并销毁
my
实例。
- 复制(或在我的示例中移动)-将返回值实例构造为
my_in_main
.
- 销毁返回值实例。
如您所见:我们在这里最多有两个副本(或一个副本和一个移动),但编译器可能会忽略它们。
据我了解,即使没有打开优化(在这种情况下),第一个副本也会被省略,过程如下:
- 称呼
function()
- 在函数中构造我的
My my;
第一个构造函数输出!
- 输出东西。函数输出!
- 复制(或在我的示例中移动)-将返回值实例构造为
my_in_main
. 移动输出!
- 销毁返回值实例。破坏输出!
在my_in_main
main 结束时破坏,给出最后的Destroy 输出!. 所以我们现在知道在未优化的情况下会发生什么。
复制省略
可以省略副本(或移动,如果该类具有移动构造函数,如我的示例中所示)。
§ 12.8 [class.copy] / 31
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。
所以现在的问题是在这个例子中什么时候发生这种情况?第一个副本的 elison 的原因在同一段中给出:
[...] 在具有类返回类型的函数的 return 语句中,当表达式是具有与函数相同的 cvunqualified 类型的非易失性自动对象(函数或 catch 子句参数除外)的名称时返回类型,可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作。
返回类型匹配返回语句中的类型:function
将My my;
直接构造成返回值。
第二次复制/移动的elison的原因:
[...] 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv 非限定类型的类对象时,可以通过构造临时类来省略复制/移动操作对象直接进入省略的复制/移动的目标。
目标类型匹配函数返回的类型:函数的返回值将被构造成my_in_main
.
所以你在这里有一个级联:
My my;
在您的函数中直接构造成直接构造成的返回值my_in_main
所以实际上您在这里只有一个对象,并且实际上function()
会(无论它做什么)对该对象进行操作my_in_main
。
- 称呼
function()
- 在函数中将 My 实例构造成
my_in_main
. 构造函数输出!
- 输出东西。函数输出!
my_in_main
在 main 结束时仍然被销毁,给出一个析构函数输出!.
这总共产生三个输出:如果打开优化,您观察到的输出。
一个不可能省略的例子。
在以下示例中,不能省略上述两个副本,因为类类型不匹配:
- return 语句与返回类型不匹配
- 目标类型与函数的返回类型不匹配
我刚刚创建了两种额外的类型:
#include <iostream>
using namespace std;
struct A
{
A(void) { cout << "A constructor " << endl; }
~A(void) { cout << "A destructor " << endl; }
};
struct B
{
B(A const&) { cout << "B copy from A" << endl; }
~B(void) { cout << "B destructor " << endl; }
};
struct C
{
C(B const &) { cout << "C copy from B" << endl; }
~C(void) { cout << "C destructor " << endl; }
};
B function() { A my; cout << "function" << endl; return my; }
int main()
{
C my_in_main(function());
return 0;
}
在这里,我们有我上面提到的“完全未优化的行为”。我会参考我在那里画的点。
构造函数(参见 2.)
功能(见 3.)
B 从 A 复制(见 4.)
析构函数(参见 5.)
C 从 B 复制(见 6.)
B 析构函数(参见 7.)
C 析构函数(main 中的实例,main 结束时销毁)