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

结果是不能编译通过的, 编译器(g++ 4.8.5)给出的错误提示大致如下:

main.cpp: In constructor ‘Foo::Foo(unique_ptr<int>&&)’: main.cpp:39:22: error: use of deleted function ‘unique_ptr<T>::unique_ptr(const unique_ptr<T>&) [with T = int]’ _member(param) ^ main.cpp:20:5: error: declared here unique_ptr(const unique_ptr & other) = delete;

我们再来看看clang++ 3.4.2给出的错误提示信息吧:

main.cpp:39:9: error: call to deleted constructor of 'unique_ptr<int>' _member(param) ^ ~~~~~ main.cpp:20:5: note: function has been explicitly marked deleted here unique_ptr(const unique_ptr & other) = delete; ^

看来这次gcc与clang算是达成一致了.

编译器的意思是说, 你试图在_member(param)这一行调用一个已经被删除的拷贝构造函数. 但是这很不符合我们的直觉: 我们认为param是一个右值引用啊, 我们试图调用的是移动构造函数, 而不是被显式删除的拷贝构造函数, 这是怎么回事呢?

原因在于, 编译器认为, param是一个左值...其内在逻辑是这样的:

param作为一个形参, 被声明为右值引用类型, 这包含了两个意思:

你只能用一个右值引用去初始化param

param本身并不是一个右值引用. 相反, 它是一个普通的左值

所以, 上面代码编译失败的原因在于: 你试图用一个左值(param)去初始化_member成员, 但_member成员所属的类, 并没有实现拷贝构造函数!

有点想骂人是吧? 所以, 核心逻辑在于, 我再换个说法再说一遍: 右值引用参数限定了只能通过右值引用去初始化这个参数, 但这个参数其实是个左值

显然这种逻辑有点不合理, 那么如何才能把我们上面的代码改正确呢? 这时候就要祭出std::move了, 如下修改即可:

class Foo { private: unique_ptr<int> _member; public: // 构造函数 Foo(unique_ptr<int> && param) : _member(std::move(param)) // <-- 你说你是左值是吧? 我把你强转成xvalue { } }; 特殊的成员函数

C++98标准定义了三个特殊的类内成员函数, 并且一直沿用至C++03标准, 这三个特殊的成员函数分别是:

X::X(const X&); 拷贝构造函数

X& X::operator=(const X&); 拷贝赋值操作符

X::~X(); 析构函数

C++11标准由于右值引用与移动语义的引入, 追加了两个特殊的类内成员函数:

X::X(X&&); 移动构造函数

X& X::operator=(X&&); 移动赋值操作符

对于特殊成员函数, 编译器在某些情况下会提供默认实现, 规则如下:

编译器仅在以上五个特殊的成员函数都没有声明实现的时候, 才会去默认给你生成一个默认的移动构造函数与默认的移动赋值操作符.

一旦你自己实现了移动构造函数, 或移动赋值操作符. 编译器就不会给你生成拷贝构造函数与拷贝赋值操作符的默认实现

那么, 在日常实践中, 应当怎么做呢? 很简单:

if(类内没有掌管任何资源) { 五个特殊成员函数一个都不用实现, 编译器自动提供的默认实现就完全够用 并且你能从其提供的默认移动构造函数与默认移动赋值操作符中获得性能提升 } else if(类掌管了资源){ if (拷贝资源的开销 > 移动资源的开销) { 五个特殊成员函数都实现一遍, 当然具体实践的时候可以采用 rule of four and a half的方式, 不实现移动赋值操作符 } else { 仅实现三个古典的特殊成员函数. 不需要实现移动构造函数和移动赋值操作符 } }

我们在copy-and-swap idom中说过, rule of five可以被简化为rule of four and a half, 这里再重温一遍, 这种最佳实践下, 你无需显式实现移动赋值操作符, 仅需要如下实现一个赋值操作符的重载即可:

X& X::operator=(X source) { swap(source); return *this; } 引用转发

来看下面这个模板函数的签名:

template<typename T> void foo(T&&);

第一眼看上去, 你可以会认为, 这个模板函数的形式参数类型是为T&&, 这显然是一个右值引用嘛, 所以你会认为: 要调用这个模板函数, 必须使用右值引用作为参数.

但实际情况是: 你竟然可以使用左值去调用这个模板函数..

#include <iostream> template<typename T> void foo(T&& t) { std::cout << t << std::endl; } int main(void) { foo(23); foo("i love you"); int a = 2333; foo(a); // <-- 可以使用一个左值去调用foo模板函数! return 0; }

这他妈的....先骂会娘..

那么到底是哪个环节出了问题呢? 明明我形参的类型写的是T&&, 是显而易见的右值引用类型的形参啊!! 问题出在模板函数的类型推导上了..这里的逻辑是这样的:

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

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