• 【读书笔记】【More Effective C++】操作符(Operators)


    条款 5:对定制的“类型转换函数”保持警觉

    • 有两种函数允许编译器进行隐式类型转换(隐式类型转换会在意想不到的地方被调用):
      • 单参数构造函数(包括除了第一个参数,所有参数都有缺省值的情况)。
      • 隐式类型转换运算符。【这是一种成员函数,如 operator double() const,该函数将类转为 double,其无需定义返回类型,因为返回类型就是这个函数的名字】
    • 避免方式:
      • 隐式类型转换运算符的避免方式比较简单(因为通常它不是类设计的必需品),用另一个功能对等的函数取代隐式类型转换运算符即可,如 double asDouble();
      • 单参数构造函数的使用往往是无法避免的,而为了避免单参数构造函数被隐式调用,可以在前添加 explicit 关键字,此时只有显式调用该构造函数才会生效,编译器就不会在意想不到的时候调用它。
    • 除了添加 explicit 关键字,还有一种方案可以更高效地避免单参数构造函数带来的隐式转换:【所谓的高效指的是用了这种技术,可以让部分隐式构造非法化】
      • 假设新设计的类如下:
        class Array{
        public:
            class ArraySize{
            public:
                ArraySize(int numElements): theSize(numElements){}
            private:
                int theSize;
            };
            Array(ArraySize size);//接收一个ArraySize对象,而非一个int
            ......
        };
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
      • 此时调用 Array(int) a(10) 来定义对象是允许的,因为编译器知道 ArraySize(int numElements) 可以将 int 转换为一个临时的 ArraySize 对象,而该对象正是 Array(ArraySize size) 需要的。
        • 这样做相当于在使用单一参数构造对象时中间加了一层转换,其中 ArraySize 类被称为 proxy(代理)类,关于代理类的相关内容会在条款 30 中进行讨论。
      • 而下述情况则无法通过:
        bool operator==(const Array<int>& lhs,const Array<int>& rls);
        Array<int> a;
        Array<int> b;
        for(int i=0;i<10;++i)
            if(a==b[i]){}//错
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • 在这种情况下,需要两次用户定制转换行为才可以,和前面的情况不一样,编译器无法知道需要两次转换还是多次转换,所以这种情况是被禁止的。

    条款 6:区别 increment/decrement 操作符的前置(prefix)和后置(postfix)形式

    • 前置式不带参数,而后置式带一个 int 参数,在调用时,编译器传递一个 0 给函数。【后置式的参数不会被使用,只是用来区分】
      • 前置式先累加后取值,后置式先取出后累加。
    • 前置返回一个引用,而后置返回一个 const 对象
      • 后置式返回对象而不是引用,因为后置式返回的是旧值。
      • 但关键是后置式为什么返回一个 const 变量,这是为了防止 i++++ 这样的式子能够通过编译,因为 i++ 返回一个临时对象,而 i++++ 的第二个后置式会作用在临时对象上,这就导致了 i 实际上只累加了一次。【为了避免这种没必要的误会,于是便将后置式返回 const 变量,这样就能避免第二次后置式的调用(为什么能避免?因为后置式是一个 non-const member function,也就是说不能传入 const 参数)】
    • 一般来说,后置式的实现都需要前置式作为基础,即后置式的实现中一般会调用前置式。

    条款 7:千万不要重载 &&、|| 和 , 操作符

    • 如题,不要重载的原因是永远无法模拟它们。
    • C++ 对 && 和 || 采取“骤死式”的评估方式。【对布尔运算的评估方式一般是骤死式】
      • 骤死式(short-circuiting behavior)的意思是:从左到右进行判定,如果左边评估完成,则右边不会进行评估。
      • 然而当重定义这些运算符时,函数调用语义取代骤死式语义。
      • 函数调用语义意味着:所有参数都必须评估完成(即所有参数都必须完成求值),各参数评估的次序未知。
    • C++ 对逗号的评估方式为:逗号左边会先评估,然后逗号的右边再被评估,最后整个逗号表达式的结果以逗号右边的值为代表。
    • 还有一些操作符不允许重载,如 new 操作符不允许重载,但是 operator new 是允许重载的。

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

    • new operator(也叫 new 表达式)和 operator new 是不同的。
      • A* a = new A(),这里的 new 是 new operator(后面统称为 new),它是 C++ 内置的,不能改变它的意义(无法重载),它总是先分配足够内存然后调用构造函数初始化内存中的对象
      • new 调用 operator new 来完成第一部分内存的分配步骤,所以允许 operator new 重载就相当于允许程序员自定义分配内存的方式。
    • operator new 通常声明为 void* operator new(size_t size),这里返回类型为 void* 因为函数返回的是一个未初始化内存的指针。
      • 程序员能够增加额外的参数重载函数 operator new,但是第一个参数类型必须是 size_t
    • 而 placement new(又叫做定位 new)和 operator new 不一样,因为 placement new 利用已经分配好的内存来构建对象。【placement new 相当于 operator new 的多一个指针参数的特别版本】
    • delete operator 和 operator delete 也是类似的不同。
      • 要注意 placement operator 不要与 delete 配套使用。
    • 当调用 new 来分配数组时,就不是调用 operator new 来分配内存,而是调用 operator new[] 来分配内存。
  • 相关阅读:
    SpringBoot 学习(八)异步任务,邮件发送和定时执行
    牛客网C语言刷题(指针篇)
    短视频文案提取的简单实现
    icmp报文及用go实现
    python 进程 (概念+示例代码)
    【前端设计模式】之装饰模式
    pdf怎么压缩?pdf压缩方法大全
    ceph源码阅读 erasure-code
    element树形组件使用之数据授权
    西门子S7-200SMART常见通讯问题解答
  • 原文地址:https://blog.csdn.net/weixin_44705592/article/details/126416013