C++右值语义的基石——完美转发
这篇文章用一个最小示例解释了为什么“参数写成右值引用”仍可能触发拷贝构造,并给出 std::forward 的正确用法与底层原理。问题本质:形参在函数体内会变成左值文章先展示了 test&& 形参仍调用拷贝构造的现象。根因是形参一旦有名字、可取地址,在函数内部就按左值处理。因此仅声明右值引用并不能自动保留原实参的值类别…
什么是完美转发? 熟悉现代C++语法的都应该清楚,C++把变量分为左值和右值,为了实现对资源的转移而不是拷贝,右值和对应的移动构造函数应运而生,但我们发现,很多时候我们并不能把左值和右值精确的传递给对应版本的函数进行处理,比如下面一个简单的代码,你会发现即使我们把函数的参数类型设置为右值引用,但当拿它去调用对应的构造函数时,它给出的竟然是拷贝构造!故这个转发还不够完美!#include<iostream> using namespace std; class test{ public: test() = default; test(test&& p){ cout<<"move construct call"<<endl; } test(const test& p){ cout<<"copy construct call"<<endl; } }; void test_fun(test&& p){ test q(p); return; } int main() { test_fun(test()); return 0; } 为什么会出现这种情况呢? 因为无论传入的形参是左值还是右值,对于函数内部来说,形参既有名称又能寻址,因此它都被认为是左值。如何实现完美转发? 实现完美转发很简单,我们在现代C++中只需要 forward<T> 这个模板函数即可完成,其实际原理就是利用的 C++11 模板中提供的折叠引用的语法,最终达到的效果就是,把参数的类型强制转换为它该有的类型,是左值就转为左值,是右值就转为右值,从而实现该调用哪个版本的函数就调用哪个版本的函数,不再只被认定为右值了! 先前的代码可以如此实现完美转发:#include<iostream> using namespace std; class test{ public: test() = default; test(test&& p){ cout<<"move construct call"<<endl; } test(const test& p){ cout<<"copy construct call"<<endl; } }; void test_fun(test&& p){ test q(forward<test>(p)); //修改的地方 return; } int main() { test_fun(test()); return 0; } 任何模板库都离不开完美转发 其实现在只要是C++的模板库,没有哪个是不用完美转发的,同时完美转发的问题也是产生自模板,而 forward 函数的实现其实也不是什么难事,实际就是利用 C++11 对模板提供的万能折叠语义:当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。 以下为一个简单的利用完美转发设计的创建工厂:#include<iostream> #include <memory> using namespace std; class test{ public: test() = default; test(int&& arg):m_iData(arg){ cout<<"move construct call"<<endl; } test(const int& arg):m_iData(arg){ cout<<"copy construct call"<<endl; } private: int m_iData; }; template<typename T,typename Arg> //不直接用T&&的原因在于,如果只使用一个模板参数会导致factory参数无法获得万能引用的效果 shared_ptr<T> factory(Arg&& arg){ return shared_ptr<T>(new T(forward<Arg>(arg)));//使用完美转发调用正确的构造函数 } int main() { int val = 5; auto p1 = factory<test>(val); auto p2 = factory<test>(5); return 0; } std::forward的实现原理 gcc 的源代码实现如下:template<typename _Tp> constexpr _Tp && forward(typename std::remove_reference<_Tp>::type &__t) noexcept { return static_cast<_Tp &&>(__t); } template<typename _Tp> constexpr _Tp…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行