C++11: 传右值引用和传左值引用_结合理解与辨析
·
传左值引用
string& operator=(const string& s)
{
// ...
}
// 传左值引用
string(const string& s)
{
// ...
}
int main()
{
string b = "11111";
string a = b; // 分析这句
return 0;
}
- 当你调用
string a = b;(拷贝构造) 或者a = b;(拷贝赋值) 时,b是一个左值(一个有名字、可以取地址的对象)—— 根据参数匹配,string a = b会匹配const 左值引用拷贝构造- 形参
s是b的一个引用,或者叫别名。s和b指向的是同一个内存地址,它们是同一个对象的两个名字。- 在函数内部对
s的操作(比如读取s._size),实际上就是对b的操作。- 关键:函数调用时,不会创建新的对象,只是传递了一个 “指针”(引用的实现通常是指针)。
- 当函数结束时,
s这个别名被销毁,但它所指向的原始对象b完全不受影响。b的生命周期由它自己的作用域决定。
传右值引用
string& operator=(string&& s)
{
// ...
}
int main()
{
string a;
a = addstring("11111","222");// addstring 返回一个string临时对象——函数的返回值,往往是被构造成临时对象,右值
return 0;
}
- 当你调用
a = addstring("","");时,addstring("","")返回的是一个临时对象。这个临时对象没有名字,是一个右值。- 形参
s是一个右值引用,它被用来绑定到这个临时对象上。- 和左值引用类似,
s也是这个临时对象的一个别名,指向同一个内存地址。- 在函数内部,你可以通过
s来操作这个临时对象。- 关键区别:这个临时对象的生命周期本来就很短,本来它的生命周期就在addstring("","")里结束,奈何移动赋值的s右值引用延长了它的生命周期。
- 当函数结束时,
s这个别名被销毁了,(因为s找个变量本身是addstring栈帧的局部变量)同时,由于没有其他任何引用或变量指向那个临时对象了,这个临时对象本身也会被销毁。(因为main栈帧再无引用去延长addstring找个临时对象的生命周期)
考一考
这是刚刚写的bug代码,不过提问:该移动赋值返回str时,调用的是什么构造呢?
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 拷贝构造" << endl;
reserve(s._capacity);
for (auto ch : s)
{
push_back(ch);
}
}
void swap(string& ss)
{
::swap(_str, ss._str);
::swap(_size, ss._size);
::swap(_capacity, ss._capacity);
}
// 移动构造
string(string&& s)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
string operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;// 提问:该移动赋值返回str时,调用的是什么构造呢?
}
int main()
{
string ret;
ret = addStrings("11111", "222");
cout << ret.c_str() << endl;
return 0;
}
注:错在哪儿?应该引用返回。因为移动赋值,本身就是两个已存在的对象直接交换即可,再拷贝构造一个已经是答案的*this(ret对象)——这会导致一个不必要的对象拷贝。你已经通过
swap高效地完成了资源转移,但return *this;这一步又额外创建了一个副本,这个副本在函数返回后就被立即销毁了,完全是一种浪费。(这个副本是临时对象,且没别名去引用它延长它的生命)
答:此处移动赋值传值返回,编译器会用
*this作为参数,调用string的构造函数—— 因为*this是左值(已存在的对象),所以最匹配的是 拷贝构造函数(string(const string& s))。—— 有名字的变量,值类别就是左值。
总结
希望本章能对你有所启发~
更多推荐


所有评论(0)