传左值引用

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))。—— 有名字的变量,值类别就是左值

总结

图1 总结

希望本章能对你有所启发~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐