《More Effective C++》读书笔记第二章:操作符

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_25467397/article/details/80686101

第二章:操作符

条款五.对定制的类型转换函数保持警觉

一个类中的隐式类型转换符的形式是:

operator type();

这是一个有点奇怪的函数:关键词operator加上一个类型名称再加上一个括号。例如有理数类:

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1);
    operator double() const;
};

这个隐式转换就将有理数的分数形式转换为小数。考虑下面的语句:

Rational r(1, 2);
cout << r;

这个时候操作符operator<<虽然无法接受Rational,但是经过隐式转换将Rational转换为double,这个时候就能调用double,实际输出就是0.5。这可能是我们没有预料到的函数调用。解决的办法就是以一个对等的函数来取代类型转换操作符

double Rational::asDouble() const;  //将Rational转换为double

直接调用这个函数就好了:cout << r.asDouble();
有一个例子说明了这个观点,在string类中,并没有类型转换操作符将其转换为char *,实际提供的是一个成员函数c_str()
另外一种隐式类型转换就是通过单自变量的构造函数来完成,例如一个Array类:

template<typename T>
class Array {
public:
    Array(int size);
    T& operator[](int index);
};

考虑对Array<int>对象的比较操作:

bool operator==(const Array<int>& lhs, const Array<int>& rhs);
Array<int> a(10), b(10);
for(int i = 0; i < 10; ++i) {
    if(a == b[i]){  //本来应该式a[i] == b[i]
        ...
    }
}

上面的代码并不会报错,原因就在于单自变量的构造函数的隐式类型转换,实际产生的代码如下:

if(a == static_cast<Array<int>>(b[i]))

避免这种类型的隐式类型转换的方法就是使用explicit关键字,explicit关键字的作用就是抑制编译器的隐式类型转换,但是显式的转换是可以的。

explicit Array(int size);
if(a == b[i]) //编译就会报错

还有一种解决办法就是使用代理类“proxy class”,例如:

class Array {
public:
    Array(ArraySize size);  //其它一样
};
class ArraySize {
public:
    ArraySize(int numEle) : theSize(numEle) {}
    int size() const { return theSize; }
private:
    int theSize;
}

这个时候ArraySize就是代理类

if(a == b[i]) //编译出错,因为无法两次调用隐式类型转换。

隐式类型转换害处大于好处,请谨慎使用。

条款六:区分前置/后置操作符

这里以前置++和后置++为例:
后置++通常会带有一个int型的形参:

T& operator++() {   //前置++
    *this += 1;    
    return *this;
}
T& operator++(int) {    //后置++
    T temp = *this; //临时对象
    ++(*this);  //调用前置++
    return temp;
}

前置++将对象作为左值返回,左值:可以取地址/有明确内存范围的值
后置++将对象的原始值副本作为右值返回,右值:无法取地址/需要存储到对象的值
举个例子:

int i = 0;
i++++;  //错误: (i++)++
++++i;  //正确:++(++i)

第一个表达式式错误的,第一个后置++返回右值,而右值调用后置++是错误的。为了避免这种情况,将后置++的返回值声明为const即可。

const T& operator++(int);

条款七:千万不要重载&& || ,这三个操作符

c++对于真假值表达式采取的评估方式是“骤死式”,即一旦表达式的真假值确定,后面未检验的部分就不会再考虑了。

char* p;
if(p != 0 && strlen(p) > 10) ...

无需担心strlen(p)中p是否为NULL。
C++允许重载&& ||操作符,但是函数调用语意会取代骤死式语意:

if(expression1 && expression2) ...

重载,那么实际是下面两种情况:

if(expression1.operator&&(expression2));
if(operator&&(exoression1, expression2));

函数调用操作执行之前,所有的参数都已经评估完成,就不存在什么骤死式语意。
对于逗号,总是左侧的先被评估,然后右侧的再被评估,最后以右侧评估结果作为代表,如果是操作符重载,不一定能达到这个效果。
无法重载的操作符:

.               .*              ::              ?:
new             delete          sizeof          typeid
static_cast     dynamic_cats    const_cast      reinterpret_cast

可以重载的操作符有:

operator new        operator delete
operator new[]      operator delete[]
+   -   *   /   %   ^   &   |   ~
!   =   <   >   +=  -=  *=  /=  %=
^=  &=  |=  <<  >>  >>= <<= ==  !=
<=  >=  &&  ||  ++  --  ,   ->* -> 
()  []

条款八:了解各种不同意义的new和delete

三种new及其对应的delete:

new operator;
operator new;
placement operator new;
  • new operator即是最常见的new操作符,C++语言内建的操作符。
    new操作符的过程就是:先配置内存,再调用构造函数(内置类型直接赋值)。(如果配置内存失败,内存还是需要释放的)
    对应的delete的过程就是:先调用析构函数(内置类型没有这一步),再释放内存
  • operator new的作用就是分配内存,函数原型为:
void * operator new(size_t size);   //size就是分配的内存的大小
  • placement operator new就是Operator new预先定义好的重载,函数原型如下:
void * operator new(size_t, void * p) { return p; }

Operator new得到的资源要进行释放,也必须使用对应的Operator delete。一般情况下都是先调用析构函数,在调用Operator delete。
new和数组。
更加底层的内容还是参见执行期语意学第二节new和delete

主要参考自侯捷老师翻译的《More Effective C++》中文版

阅读更多

没有更多推荐了,返回首页