C++ 与move构造函数配对时发生意外的析构函数调用
以下代码是在Visual Studio 2012 Express for Windows Desktop中编译和运行的,作为学习练习C++ 与move构造函数配对时发生意外的析构函数调用,c++,visual-c++,c++11,visual-studio-2012,move-semantics,C++,Visual C++,C++11,Visual Studio 2012,Move Semantics,以下代码是在Visual Studio 2012 Express for Windows Desktop中编译和运行的,作为学习练习 #include <cstdio> class X { public: X() { printf("default constructed\n"); } ~X() { printf("destructed\n");} X(const X&) { printf("copy constructed\n"); }
#include <cstdio>
class X
{
public:
X() { printf("default constructed\n"); }
~X() { printf("destructed\n");}
X(const X&) { printf("copy constructed\n"); }
X(X&&) { printf("move constructed\n"); }
X & operator= (const X &) { printf("copy assignment operator\n"); }
};
X A() {
X x;
return x;
}
int main() {
{
A();
}
std::getchar();
}
这表明命名的返回值优化已经省略了对move构造函数的调用,并掩盖了潜在的问题
实验2:禁用优化并注释掉move构造函数。生成的输出是我所期望的
default constructed
copy constructed
destructed
destructed
A中的X在超出范围时被销毁 A返回一个临时对象(由move构造函数从X构造),它是一个单独的实例。这在调用方的作用域中被销毁。这将导致再次调用析构函数(临时调用) 之所以选择移动构造函数,是因为编译器检测到X将立即被销毁。要使用这种方法,移动构造函数应该使原始对象中的任何数据无效或重置,以便析构函数不会使移动目标接收的任何数据无效 当您通过值传递右值,或通过值从函数返回任何内容时,编译器首先获得删除副本的选项。如果副本未被省略,但所讨论的类型具有移动构造函数,则编译器需要使用移动构造函数 当您退出创建临时对象的作用域时,它将被销毁。如果引用绑定到临时对象,则当引用超出范围时,临时对象将被销毁,除非由于控制流中断而提前销毁 RVO可以产生与非优化版本不同的行为:
返回值优化,或者简单的RVO,是一种编译器优化技术,包括消除为保持函数返回值而创建的临时对象。(1)在C++中,特别值得注意的是,允许改变所得到的程序的可观察行为。[2 ] < /P>
A中的X在超出范围时被销毁 A返回一个临时对象(由move构造函数从X构造),它是一个单独的实例。这在调用方的作用域中被销毁。这将导致再次调用析构函数(临时调用) 之所以选择移动构造函数,是因为编译器检测到X将立即被销毁。要使用这种方法,移动构造函数应该使原始对象中的任何数据无效或重置,以便析构函数不会使移动目标接收的任何数据无效 当您通过值传递右值,或通过值从函数返回任何内容时,编译器首先获得删除副本的选项。如果副本未被省略,但所讨论的类型具有移动构造函数,则编译器需要使用移动构造函数 当您退出创建临时对象的作用域时,它将被销毁。如果引用绑定到临时对象,则当引用超出范围时,临时对象将被销毁,除非由于控制流中断而提前销毁 RVO可以产生与非优化版本不同的行为:
返回值优化,或者简单的RVO,是一种编译器优化技术,包括消除为保持函数返回值而创建的临时对象。(1)在C++中,特别值得注意的是,允许改变所得到的程序的可观察行为。[2 ] < /P>
请记住,当对象是移动操作的源时,它仍将被销毁。因此,移动源需要将其自身置于这样一种状态,即被破坏不会释放它不再拥有的资源(因为它们已被移动到另一个对象)。例如,源对象中的任何原始指针(现在将由移动构造的对象拥有)都应设置为NULL。请记住,当对象是移动操作的源时,它仍将被销毁。因此,移动源需要将其自身置于这样一种状态,即被破坏不会释放它不再拥有的资源(因为它们已被移动到另一个对象)。例如,源对象中的任何原始指针(现在将由move构造的对象拥有)都应设置为NULL。尽管Michael和jspcal的答案是准确的,但它们没有回答我问题的核心,这就是为什么会有两个析构函数调用。我只期待一个 答案是函数A()返回一个临时对象。总是。这就是函数返回值的工作方式,而移动语义与此事实无关。我猜Michael和jspcal认为我没有错过这样一个基本事实。我将“移动”一词等同于“交换”的概念。交换时,不会构造和销毁对象。因此,我只期待一个析构函数调用 由于返回的对象必须构造和析构函数,因此进行了第二次析构函数调用(和第二次构造函数调用)
现在,选择要执行的实际构造函数取决于类定义中提供的内容。如果移动构造函数可用,则将调用该构造函数。否则将调用复制构造函数 虽然Michael和jspcal的回答是准确的,但他们没有回答我问题的核心,这就是为什么有两个析构函数调用。我只期待一个 答案是函数A()返回一个临时对象。总是。这就是函数返回值的工作方式,而移动语义与此事实无关。我猜Michael和jspcal认为我没有错过这样一个基本事实。我将“移动”一词等同于“交换”的概念。交换时,不会构造和销毁对象。因此,我只期待一个析构函数调用 因为返回的对象必须被构造和分解
default constructed
destructed
default constructed
copy constructed
destructed
destructed