最近在看c++ 11标准中的左值,右值相关的知识,完美传递,通用引用。将通过实例代码,来学习其中的规则。水平有限,欢迎探讨。
Windows 10,vs2017 ;ubuntu18.04.1(WSL); g++ 7.4.0
编译器对标准的支持
手册地址
右值是在c++11标准才提出来的,原文
& attr(optional) declarator (1)
&& attr(optional) declarator (2) (since C++11)
- Lvalue reference declarator: the declaration S& D; declares D as an lvalue reference to the type determined by decl-specifier-seq S.
- Rvalue reference declarator: the declaration S&& D; declares D as an rvalue reference to the type determined by decl-specifier-seq S.
右值引用可用于为临时对象延长生存期(注意,左值引用亦能延长临时对象生存期,但不能通过左值引用修改它们)
#include
using namespace std;
template<typename T>
void print(T& t) {
cout << "lvalue" << endl;
}
template<typename T>
void print(T&& t) {
cout << "rvalue" << endl;
}
template<typename T>
void TestForward(T && v) {
print(v);
print(std::forward<T>(v));
print(std::move(v));
}
void test_rvalue() {
std::string s1 = "s1";
std::string s2 = "s2";
std::string &&s3 = s1 + s2;// s1和s2累加之后产生的临时对象将可以被s3格式变量接受
// std::string &&s4 = s1; // 不被允许的语法,不能直接将左值赋值给右值
print(s1);//lvalue
print(s2);//lvalue
print(s3);//lvalue
print(s1 + s2);//rvalue
}
int main() {
test_rvalue() ;
TestForward(11);// void TestForward(int &&v)
int x = 11;
TestForward(x);// void TestForward(int &v)
TestForward(std::forward<int>(x));// void TestForward(int &&v)
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(modern_cpp)
file(GLOB_RECURSE SRC_CODE src *.cpp *.h *.hpp )
set(CMAKE_CXX_STANDARD 14)
add_executable(test_smart_ptr ${SRC_CODE})
这段代码里面在void TestForward函数的参数表,使用了通用引用方式参数。
通用引用(universal reference)是Scott Meyers在C++ and Beyond 2012演讲中自创的一个词,用来特指一种引用的类型。参考地址
里面存在多个&&,在函数推导的时候,将会造成引用折叠(坍缩)。之后将会获得一个简化的版本的函数出来。在进入TestForward函数中,将会使用到T,可能就不是预想的那个类型了。所以看懂这段函数需要动一些脑筋。
TestForward(std::forward(x));
这个语句将将x转换成一个int &&的右值对象传到TestForward中,&& &&经过引用折叠之后,将会还保持&&,也就是保持了右值引用。
TestForward(x);
将会把x以int &方式带入,& &&引用折叠之后,将会得到&
参考资料
Concise explanation of reference collapsing rules requested:
(1) A& & -> A& , (2) A& && -> A& , (3) A&& & -> A& , and (4) A&& && -> A&&
可以这样记住,就是只要参与其中的,有单个&,一定就是&,只有将两个&& &&,才会产生&&右值引用。
void test_rvalue() {
std::string s1 = "s1";
std::string s2 = "s2";
std::string &&s3 = s1 + s2;// s1和s2累加之后产生的临时对象将可以被s3格式变量接受
// std::string &&s4 = s1; // 不被允许的语法,不能直接将左值赋值给右值
print(s1);//lvalue
print(s2);//lvalue
print(s3);//lvalue
print(s1 + s2);//rvalue
}
s3最后还是被推导成左值,右值是一种无法取到地址的,临时的
源代码中
TestForward(11);
TestForward(x);
TestForward(std::forward(x));
TestForward(std::forward
在执行过程中,将全部输出:
rvalue
也就是说,std::move,将会强行将变量引用转换成右值引用。
如果编写代码不注意,将会造成很多右值被赋值给左值,并且通过左值再去做传递,这样的情况下,将会造成很多不必要的对象深度拷贝。
#include
#include
using namespace std;
struct A
{
A(int _age):age(_age) { cout << "A::A(int _age)\n"; }
A() { cout << "A::A()\n"; }
~A() { cout << "A::~A()\n"; }
int age{ 0 };
};
void test_move(std::list<A> &ret_lst) {
std::list<A> tmp_lst;
for (int i = 0; i< 100;i++)
{
tmp_lst.emplace_back(i);
}
cout << "emplace_back finish\n";
ret_lst = std::move(tmp_lst);
cout << "ret_lst size: " << ret_lst.size() << "\n";
cout << "tmp_lst size: " << tmp_lst.size() << "\n";
}
int main() {
std::list<A> ret_lst;
test_move(ret_lst);
return 0;
}
代码中将tmp_lst指针拿出来,并且将,比那种深度拷贝要速度快很多。可以理解成,将一个左值转换成右值。而且会清空掉之前左值里面的内容(身体被掏空)。
源代码中
TestForward(11); // rvalue
TestForward(x); // lvalue
TestForward(std::forward(x)); //
TestForward(std::forward
在执行过程中,将全部输出:
rvalue
概念
此示例演示参数到类 T 构造函数实参的完美转发。并展示参数包的完美转发。
#include
#include
#include
struct A {
A(int&& n) { std::cout << "rvalue overload, n=" << n << "\n"; }
A(int& n) { std::cout << "lvalue overload, n=" << n << "\n"; }
};
class B {
public:
template<class T1, class T2, class T3>
B(T1&& t1, T2&& t2, T3&& t3) :
a1_{std::forward<T1>(t1)},
a2_{std::forward<T2>(t2)},
a3_{std::forward<T3>(t3)}
{
}
private:
A a1_, a2_, a3_;
};
template<class T, class U>
std::unique_ptr<T> make_unique1(U&& u)
{
return std::unique_ptr<T>(new T(std::forward<U>(u)));
}
template<class T, class... U>
std::unique_ptr<T> make_unique2(U&&... u)
{
return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}
int main()
{
auto p1 = make_unique1<A>(2); // 右值
int i = 1;
auto p2 = make_unique1<A>(i); // 左值
std::cout << "B\n";
auto t = make_unique2<B>(2, i, 3);
}
完美转发其实是建立在reference collapsing之上的,每次std::forward将会按照变量输出值,做推论(是左值还是右值),而不会如同std::move,粗暴将类型直接强转成右值。然后通过T&&的reference collapsing,就能完美的将数据全部都通过模板函数全部都初始化到对象中。p1,p2这一级的初始化,会按照左值或者右值来处理,而最后make_unique2的时候,也在B的构造器,按照之前的左值、右值将按照数据完美传递到B对象中的A成员变量。