13.6 对象移动
13.6.1 右值引用
概念: 为了支持移动操作,新标准引入了的一种新的引用类型.所谓右值引用就是必须绑定到右值的引用. 通过&&
来获得右值引用
int i = 42;
int &r = i;
int &&rr = i; // 错误,不能将一个右值引用绑定到一个左值上
int &r3 = i * 42; // 错误,i*42是个右值
const int &r3 = i * 42;
int &&rr2 = i * 42;
标准库move函数
虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型. 可以通过调用一个名为move
的新标准库函数来获得绑定到左值上的右值引用
int &&rr1 = 42;
int &&rr3 = std::move(rr1);
移动构造函数和移动赋值运算符
移动构造函数的第一个参数是该类类型的一个引用.这个引用参数在移动构造函数中是一个右值引用
移动赋值运算符执行与析构函数和移动构造函数相同的工作
重载和引用函数
class Foo {
public:
Foo &operator=(const Foo&) & {
return *this;
}
// 可用于可改变的右值
Foo sorted() && {
sort(data.begin(), data.end());
return *this;
}
// 可用于任何类型的foo
Foo sorted() const & {
Foo ret(*this);
sort(ret.data.begin(), ret.data.end());
return ret;
}
private:
vector<int> data;
};
Foo &retFoo() {
Foo tmp;
Foo &ret = tmp;
return ret;
}
Foo retVal() {
Foo ret;
return ret;
}
int main()
{
retVal().sorted();
retFoo().sorted();
return 0;
}
14.1 基本概念
在什么情况下重载的运算符与内置运算符有所区别?在什么情况下重载的运算符又与内置运算符一样?
我们可以直接调用重载运算符函数。重置运算符与内置运算符有一样的优先级与结合性。
ring 和 vector 都定义了重载的== 以比较各自的对象,假设 svec1 和 svec2 是存放 string 的 vector,确定在下面的表达式中分别使用了哪个版本的==?
a) “cobble” == “stone”
(b) svec1[0] == svec2[0]
(c ) svec1 == svec2
(d) "svec1[0] == “stone”
(a) 都不是。
(b) string
(c ) vector
(d) string
14.2 输入和输出运算符
14.2.1 重载输出运算符<<
第一个形参是一个非常量的ostream
对象的引用, 第二个形参一般来说是一个常量的引用; 一般要返回它的ostream
形参
ostream &operator<<(ostream &os, const Sales_data &item) {
os << item.isbn() << " " << item.units_old << " " << item.revenue << " " << item.avg_price();
return os;
}
输入输出运算符必须是非成员函数, 而不能是类的成员函数. 假设输入输出运算符是某个类的成员,则它们也必须是istream
或ostream
的成员. 然而,这两个类属于标准库,并且我们无法为标准库中的类添加任何成员
14.2.2 重载输入运算符
第一个形参运算符将要读取的流的引用,第二个形参是将要读入到的(非常量)对象的引用
istream &operator >> (istream &is, Sales_data &item) {
double price;
is >> item.bookNo >> item.unites_sold >> price;
if (is) {
item.revenue = item.units_sold * price;
} else {
item = Sales_data();
}
return is;
}
14.3 算术和关系运算符
通常,我们把算术和关系运算符定义非成员函数以允许对左侧或右侧的运算对象进行转换;
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
Sales_data sum = lhs;
sum += rhs;
return sum;
}
14.3.1 相等运算符
用来检验两个对象是否相等
bool operator ==(const Sales_data &lhs, const Sales_data &rhs) {
return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.unites_sold && lhs.revenue == rhs.revenue;
}
bool operator !=(const Sales_data &lhs, const Sales_data &rhs) {
return !(lhs == rhs)
}
14.4 赋值运算符
和拷贝赋值及移动赋值运算符一样,其他重载赋值运算符也必须释放当前内存空间,再创建一片新空间.
StrVec &StrVec::operator=(initialize_list<string> il)
{
auto data = alloc_n_copy(il.begin(), il.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
复合赋值运算符
Sales_data &Sales_data::operator += (const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rsh.revenue;
return *this;
}
14.5 下标运算符
表示容器的类通常可以通过在容器中的位置访问元素, 这些类一般会定义下标运算符[]
, 下标运算符必须是成员函数
class StrVec {
public:
std::string &operator[](std::size_t n) { return elements[n];}
const std::string &operator[](std::size_t n) const { return elements[n];}
private:
std::string *elements;
}
14.6 递增和递减运算符
两种运算符使得类可以在元素的序列中前后移动. C++
语言并不要求递增和递减运算符必须是类的成员,但是因为它们改变的正好是所操作对象的状态,所以建议将其设定为成员函数
class StrBlobPtr {
public:
StrBlobPtr &operator++(); //前置运算符
StrBlobPtr &operator--();
};
区分前置和后置运算符
后置版本接受一个额外的(不被使用)int类型的形参.
class StrBolbPtr {
public:
StrBlobPtr operator++(int); //后置运算符
StrBlobPtr operator--(int);
};
显式地调用后置运算符
StrBlobPtr p(a1);
p.operator++(0); // 后置版本的operator++;
p.operator++(); // 前置版本的operator++;
14.7 成员访问运算符
class StrBlobPtr {
sdt::string &operator*() const {
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
std::string *operator->() const {
return & this->operator*();
}
};
14.8 函数调用运算符
如果类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象
struct absInt {
int operator()(int val) const {
return val < 0 ? -val : val;
}
}
int i = -42;
absInt absObj;
int ui = absObj(i);
14.8.1 lambda是函数对象
在lambda
表达式产生的类中含有一个重载的函数调用运算符
string words;
stable_sort(word.begin(), word.end(), [](const string &a, const string &b)
{return a.size() < b.size();});
// 其行为类似于下面这个未命名对象
class ShorterString {
public:
bool operator() (const string &s1, const string &s2) const {
return s1.size() < s2.size();
}
};
// 用类代替lambda表达式后
stable_sort(word.begin(), word.end(), ShorterString());
14.8.2 标准库定义的函数对象
14.8.3 可调用对象与function
不同类型可能具有相同的调用形式
// 普通函数
int add(int i, int j) {return i + j;}
// lambda,产生一个未命名函数对象类
auto mod = [](int i, int j){ return i % j;}
// 函数对象类
struct divide {}
int operator()(int denominator, int divisor) {
return denominator / divisor;
}
;
// 构建从运算符到函数指针的映射关系,其中函数接受两个int,返回一个int
map<string, int(*)(int, int)> binops;
但是我们不能将mod或者divide存入binops,因为mod是个lambda
表达式,而每个lambda有它自己的类类型,与存储在binops
中的值的类型不匹配
标准function
map<string, function<int(int,int)>> binops = {
{"+", add},
{"-", std::minus<int>()},
{"/" devide()},
{"*", [](int i, int j){return i * j;}},
{"%", mod}
};
我们的map
中包含5个元素,尽管其中可调用的类型各不相同,我们仍然能够把所有这些类型都存储在一个function<int(int,int)>
类型中