第13章 拷贝控制
使用default:=defult只能修饰默认构造函数或拷贝控制成员,显式地要去编译器生成合成的版本。
使用delete:=delete通知编译器不希望定义这些成员,禁止试图使用它的操作,通常的用途是禁止拷贝控制成员,或引导函数匹配。
析构函数不能是delete的,如果删除了析构函数,我们只能动态分配这种类型,并且不能释放这些对象。(非动态类型会被系统自动释放)
定义行为像值的类:如果将一个对象赋予它自己,赋值运算符必须能正确工作(对象内含指针的时候);大多数赋值运算符组合了析构函数和拷贝函数的工作。
class P{ public: //各个函数 private: std::string &ps; int i; }; P& P::operator = (const P &rhs){ auto newp = new string(*rhs.ps); delete ps; ps = newp; i = rhs.i; return *this; }
定义行为像引用的类:指针指向同一块内存
class P{ public: //各个函数 private: std::string &ps;//拷贝、赋值时,ps指向同一块内存 int i; std::size_t *use;//*use表示引用计数,*use = 0时释放内存 };
定义swap:
定义swap的意义在于类中存在指针的话,指针指向的内存可能会再次得到分配(如果定义的是行为像值的类),而实际上交换指针效率更高。
class P{ friend void swap(P& , P&); //其他成员定义 }; inline void swap(P &lhs, P &rhs){ using std::swap; swap(lhs.ps, rhs.ps);//使用自己定义的swap交换指针类型, 匹配程度优于std::swap //交换其他成员 } //下述代码与默认的swap效率无异,应当使用自己定义的swap //inline void swap(P &lhs, P &rhs){ // std::swap(lhs.ps, rhs.ps);//使用了标准库的swap //}
通过swap重载 =
//rhs是按值传递的, 自动处理了自赋值情况且天然就是异常安全的 P& P::operator = (P rhs){ swap(*this, rhs); return *this; }
对象移动:
右值引用:必须绑定到右值的引用。右值引用只能绑定到一个将要销毁的对象。
通过右值引用,可以完成移动构造函数和移动赋值运算符。
std::move()获得绑定到左值上的右值引用。
只有当类没有定义自己版本的拷贝控制成员且类的每个非static数据成员都可以移动,编译器才会为它合成移动构造函数或移动赋值运算符。
定义了一个移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作,否则这些成员默认被定义为删除的。
一些标准库,包括string都定义了移动构造函数。
如果类既有移动构造函数又有拷贝构造函数,则通过函数匹配规则来确定使用哪个构造函数,赋值操作类似;如果没有移动构造函数,右值也被拷贝。
个人感觉,移动构造函数类似于通过把右值的指针赋值过去,并修改右值的指针为空来提高效率。
重载和引用函数:类似于const函数,在参数列表后放置一个引用限定符(&或&&)指出this的左值/右值属性的方式。如果一个成员函数有引用限定符,则具有相同参数列表的所有版本都必须有引用限定符。
第14章 重载运算与类型转换
把二元运算重载为成员函数时,运算的 LHS 是 *this。如果 LHS 为基本类型或是不可修改源代码的类型,就需要重载为非成员函数(如IO类)。
重载为成员函数时,无参则重载的是一元运算符,有1个参数则重载的是二元运算符。
前置++: a.operator++();
后置++: a.operator++(0);
重载成员访问符:P &operator*(), P *operator->(), 解引用通常是类的成员,返回引用;箭头运算符必须是类的成员,必须返回类的指针或重载了箭头运算符的类的对象(即返回的值还能进行箭头运算)。
函数调用运算符:定义了调用操作符的类,其对象称为函数对象,我们说这些对象的行为像函数一样。
struct absInt{ int operator()(int val) const { return val < 0? -val: val; } }; absInt absObj; int x = absObj(-10); // x = 10;
函数对象常常作为泛型算法的实参。
sort接受第三个参数为二元谓词函数或函数指针,而priority_queue接受第三个参数为类型。
一般算法接受的是函数对象或函数指针,而容器适配器接受的是一个class类型。
如:
bool cmp(const int &a, const int &b){ return a < b ; } struct mycmp { bool operator() (int i,int j) { return (i<j);} }; int main(){ bool (*fun)(const int &, const int &) = cmp; vector<int> ve; //调用函数指针 sort(ve.begin(), ve.end(), fun); sort(ve.begin(), ve.end(), cmp); sort(ve.begin(), ve.end(), mycmp()); //lambda表达式 sort(ve.begin(), ve.end(), [](const int &lhs, const int &rhs){ return lhs < rhs;}); sort(ve.begin(), ve.end(), greater<int>() ); //ve存int, 按降排列, 第3个参数是greater<int>类型的一个未命名对象。 priority_queue<int, vector<int>, greater<int> > Q; int a = greater<int>()(3, 5); // a = false, greater<int>是一个类, greater<int>()是一个对象/函数名, greater<int>()(3, 5)是调用函数 return 0; }
标准库定义的函数对象:less<Type>, greater<Type>, plus<Type>等
标准库function类型:function<T> f; 如function<int(int, int)> f1 = add;//声明了一个function类型,可以表示接受两个int,返回一个int的可调用对象。
类型转换运算符:operator type() const;是一种特殊成员函数,复制将类类型的值转换为其他类型。
//构造函数将int转换为类,类型转换符将类转换为int class SmallInt{ public: SmallInt(int i = 0): val(i){ if(i < 0||i > 255) throw std::out_of_range("Bad SmallInt value"); }; operator int() const { return val;} private: std::size_t val; }; SmallInt si; si = 4; si = si+3;
注意: int i = 42; cin << i; //cin会转换为bool类型。
为了防止上述情况发生,可用显式的类型转换符,用explicit修饰,如explicit operator int() const { return val;}
但在表达式中被用作条件时,编译器会自动进行显示的类型转换。
operator bool()一般被定义为explicit的。
避免有二义性的类型转换。
第15~16章 略