C++ Primer第十三章⑧
C++ Primer
拷贝控制
对象移动
我们一般会同时定义拷贝和移动两个操作,这样好处大大滴,以push_back函数为例:
void push_back(const X&); //拷贝:可以绑定到任意类型的X(因为加了const)
void push_back(X&&); //移动:只能绑定到类型为X的可修改的右值
区分移动和拷贝的重载函数通常有两个版本:一个接受const T&,另一个接受T&&
再来个具体的例子:
class StrVec
{
public:
void push_back(const string&); //拷贝元素
void push_back(string&&); //移动元素
};
void StrVec::push_back(const string& s)
{
chk_n_alloc(); //确保有新空间
alloc.construct(first_free++, s); //插入s,并后移first_free
}
void StrVec::push_back(string&& s)
{
chk_n_alloc(); //确保有新空间
alloc.construct(first_free++, std::move(s)); //插入s,并后移first_free
}
写完后我们来调用试试:
StrVec vec;
string s = "some thing";
vec.push_back(s); //调用push_back(const string&),是拷贝的,s可能还要用的
vec.push_back("done"); //调用push_back(string&&),是移动的,"done"就不用了
来看点关于左值和右值神奇的东西
我们来看点看上去非法的,但是编译器不会报错的:
string s1 = "a", s2 = "b";
auto n = (s1+s2).find('a'); //我们居然在一个右值上调用函数,右值代表值啊,
然而这是可以的。。。主要是因为新旧标准问题
s1 + s2 = "fff"; //这也行的。。。
在新标准中,向右值赋值还是算合法的。。。
我们虽然不能去动标准库(它是允许向右值赋值的),但是我们可以在自己的地盘自己定义的类中阻止这种不合理的方式-我们希望强制左侧运算对象(this)是一个左值。
那我们具体该怎么做呢?我们可以在赋值运算符参数列表后面加一个引用限定符,&表示this指向左值,&&表示右值;而且,引用限定符只能用于(非static)成员函数,且必须同时出现在函数的声明和定义中:
class Foo
{
public:
Foo &operator=(const Foo&) &; //拷贝赋值运算符,且只能给左值赋值
};
Foo &Foo::operator(const Foo &rhs) & //声明和定义中都要有引用限定符
{
return *this;
}
好的,我们来用下试试:
Foo &retFoo(); //函数声明,返回引用,是左值
Foo retVal(); //返回右值
Foo i, j;
i = j; //正确:i是左值
retFoo() = j; //正确:retFoo返回左值
retVal() = j; //错误
i = retVal(); //正确
记不记得我们还可以在参数列表后面放const,表示该函数不能修改类中的成员变量,我们得把&放在const之后:
Foo anothr() const &{};
重载
我们直接抛代码:
class Foo
{
public:
Foo sorted() &&; //用于可改变的右值
Foo sorted() const &; //可用于任意类型的Foo
private:
vector<int> data;
}
Foo Foo::sorted() &&
{
sort(data.begin(), data.end());
return *this;
}
Foo Foo::sorted() const &
{
Foo ret(*this); //我们无法改变this,所以要拷贝个副本来排序返回
sort(ret.data.begin(), ret.data.end());
return ret
}
编译器会通过函数匹配来确定调用哪个:
retVal().sorted(); //retval()返回右值,调用Foo::sorted() &&
retFoo().sorted(); //调用左值那个
注意,引用限定符是这样的,重载函数,要不你都加,要不就都不加,非常讲义气。