对C++11中的`移动语义`与`右值引用`的介绍与讨论 (10)

更诡异的事情在下面: 你按值将一个函数内部的自动变量返回的时候, 编译器都会进行资源的移动操作!!!

unique_ptr<Shape> make_square() { unique_ptr<Shape> result(new Square); return result; } \----/ | | 编译器将result的资源交给了d, 是移动 | 是的, 是那种既不调移动构造函数, 也不调拷贝构造函数的, 诡异的移动 v unique_ptr<Shape> d(make_square());

这个编译器的鬼逻辑是这样的: 虽然从函数内部看, result是一个变量, 一个左值, 但从函数外部调用来看, result的生命周期也是短暂的, 函数调用结束后, 它就不存在了. 这和临时量很像, 所以, 我将这个result中的资源剥夺出来, 是一种安全的行为.

从下面这个简单而又完整的例子你就会看到: 函数返回时, 返回值本身就是按移动返回的, 这种移动甚至更高级: 被返回的变量, 无论是自动变量还是临时变量, 其实并没有在函数退栈的时候被析构, 这种被返回的变量, 是真真切切的存在于内存中, 只是把其名字改成了返回值的接收者! 这个点并不容易被人理解, 特别是对函数调用在汇编层面上的原理不熟悉的人来说, 显得特别诡异.

#include <iostream> struct POD{ int _f1; int _f2; }; class Foo { public: POD* _pod; public: // 默认构造函数 Foo() { _pod = new POD{1,2}; std::cout << "constructor: _pod == " << _pod << std::endl; } // 拷贝构造函数 Foo(const Foo & foo) { std::cout << "copy constructor" << std::endl; _pod = new POD{ foo._pod->_f1, foo._pod->_f2, }; } // 移动构造函数 Foo(Foo && foo) { std::cout << "move constructor" << std::endl; _pod = foo._pod; foo._pod = nullptr; } // 拷贝赋值操作符 Foo& operator = (const Foo & foo) { if(this != &foo) { _pod = new POD{ foo._pod->_f1, foo._pod->_f2, }; } return *this; } // 移动赋值操作符 Foo& operator =(Foo && foo) { _pod = foo._pod; foo._pod = nullptr; return *this; } // 析构函数 ~Foo() { std::cout << "destructor: _pod == " << _pod << std::endl; delete _pod; _pod = nullptr; } }; Foo ReturnAutoVariableFooFromFunc() { std::cout << "create auto variable inside func then return it" << std::endl; Foo foo; // 调用默认构造函数 return foo; // <- 这里并没有对foo这个内部变量的析构操作 } Foo ReturnTempVariableFooFromFunc() { std::cout << "create temp variable inside func then return it" << std::endl; return Foo(); // <- 这里也并没有对Foo()表达式创建的临时变量的析构操作 } int main(void) { Foo foo = ReturnAutoVariableFooFromFunc(); std::cout << "foo._pod outside function == " << foo._pod << std::endl; std::cout << "--------------------" << std::endl; Foo foo2(ReturnAutoVariableFooFromFunc()); std::cout << "foo2._pod outside function == " << foo2._pod << std::endl; std::cout << "--------------------" << std::endl; Foo foo3 = ReturnTempVariableFooFromFunc(); std::cout << "foo3._pod outside function == " << foo3._pod << std::endl; std::cout << "--------------------" << std::endl; Foo foo4(ReturnTempVariableFooFromFunc()); std::cout << "foo4._pod outside function == " << foo4._pod << std::endl; std::cout << "--------------------" << std::endl; return 0; }

这段代码的输出长下面这样:

create auto variable inside func then return it constructor: _pod == 0x1ae9010 foo._pod outside function == 0x1ae9010 <- 观察这里, 并没有对析构函数的调用, 并没有对拷贝构造函数或移动构造函数的调用 -------------------- create auto variable inside func then return it constructor: _pod == 0x1ae9030 foo2._pod outside function == 0x1ae9030 <- 观察这里, 并没有对析构函数的调用, 并没有对拷贝构造函数或移动构造函数的调用 -------------------- create temp variable inside func then return it constructor: _pod == 0x1ae9050 foo3._pod outside function == 0x1ae9050 <- 观察这里, 并没有对析构函数的调用, 并没有对拷贝构造函数或移动构造函数的调用 -------------------- create temp variable inside func then return it constructor: _pod == 0x1ae9070 foo4._pod outside function == 0x1ae9070 <- 观察这里, 并没有对析构函数的调用, 并没有对拷贝构造函数或移动构造函数的调用 -------------------- destructor: _pod == 0x1ae9070 destructor: _pod == 0x1ae9050 destructor: _pod == 0x1ae9030 destructor: _pod == 0x1ae9010 <- 这里倒是有四个次析构, 不过这是由于main函数退栈而对 foo/foo2/foo3/foo4 的析构

然而, 更大的坑在这里: C++11引入了右值引用, 我能不能手动的, 显式的返回一个右值引用, 将函数内部的临时量, 或自动变量的资源, 交给调用者呢? 答案是: 不行.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzspdp.html