《More Effective C++》- 极精简版 21-30条

本文介绍了C++编程中的关键技巧,包括如何通过重载技术避免隐式转换,使用复合操作符提升性能,选择合适的时间更换第三方库,理解虚函数和RTTI的成本,以及使用智能指针、引用计数和代理类进行高效和安全的内存管理。
摘要由CSDN通过智能技术生成

        本文章属于专栏- 概述 -《业界C++进阶建议整理》-CSDN博客


          本章我会继续讲解我对21-30条的极精简的理解。

  • 21、利用重载技术避免隐式转换
    • const A operator+(const A& a1, const A& a2) ,且有构造函数 A(int){}
    • 那 A a3 = 2 + a2;时,会先将2隐式转换为A类型的临时变量,然后调用函数。
    • 这里可以通过增加 const A operator+(int a1, const A& a2)来防止隐式转换产生临时变量
    • 但是注意,重载操作符,至少获取一个用户定制类型,不能是const A operator+(int a1, int a2)
    • 个人见解:在业务代码中,尽量使用explicit不要让隐氏转换发生。
  • 22、使用操作符,考虑用复合形式(op=)替换独身形式(op)
    • 如 +=是在自身对象上操作,没有临时对象的构造和析构。所以尽量用+=
    • 当然有时候为了代码的可读性,使用 A a1 = a2 + a3 + a4 + a5;也是可以的,在于这段代码的使用频率,是不是性能优化的重点
    • 另外为了,代码逻辑方便维护,实际加法逻辑只有一份, operator+ 调用operator+=是一个很有意义的方法。
  • 23、考虑使用其他程序库
    • 个人见解:在用三方库时,不受惯性思想,搜一下当前时间点,性能最好,最稳定的库
  • 24、了解virtual functions、multiple inheritance、virtual base classes、runtime type identification成本
    • 虚函数成本:
      • 虚函数表:每个包含虚函数的类都有一个虚函数表,表中存的是指向虚函数的地址,虚函数表的大小和虚函数个数相关。
      • 即使是多继承、或者virtual继承,虚函数本身的开销不会更大,反而是它不能inline关系更大
      • 虚函数放弃了inline(两个语义本身就冲突,虚函数运行时调用,inline编译期用函数体替换)
      • 虚函数调用就是,通过指针或者引用,找到对象的虚函数表指针,然后找到需要调用的虚函数表中的虚函数指针,执行函数。
      • 虚函数指针:每个对象都有一个虚函数指针
    • RTTI成本:
      • 作用:运行时,获取objects和classes的信息。
      • 原理:存放在每个对象的type_info结构中,这个结构由编译器生成 。一个class只需要一份RTTI,这个结构在很多编译器的实现中,放到了虚函数表中
  • 25、虚拟化构造函数和非成员函数
    • 将std::vector<Base*>的内容复制到新的vector(这里是复制内容,不是指针),不能直接调用拷贝构造函数,因为不知道具体是哪个class,这里需要一个clone函数。如基类是Base* clone() {return *this} 派生类Derive* clone() {return *this}
    • 非成员函数虚拟化,是为了多态
  • 26、限制class能产生的数量
    • 生产0个:将构造函数放到private
    • 生产1个:写一个返回对象的static函数(有static成员的函数不能用inline,每个调用函数的地方,都可能有自己的static成员变量),函数中有一个static成员
    • 生产多个:class有自己的计数器,有自己的工厂函数,有一个自己的static成员,进行计数。
      • 用一个template基类来做(注意一定要用模板基类,static计数,被所有派生类公用)
  • 27、要求、禁止对象产生于heap中
    • 要求对象必须产生在heap中,将构造函数和析构函数放到protect(private中,会影响继承)
    • 但是这样的话,派生类可以生成在栈上,其基类部分自然也在栈上
    • 想要派生类也保证只在heap上声明,利用的是operator delete,让其只删除通过operator new出来的东西,覆盖operator new(让其只从堆申请内存,并存放到类的list结构中),而delete时,去判断delete的内容是否来自于堆。
      • 这样的做法,在编译期间没有办法发现,需要在运行时候,通过异常发现(由delete抛出)
    • 个人见解:这样的需求,对于业务代码来说,有些纸上谈兵,实际中要求对象必须生产在堆中或者不在堆中,由开发者来保证是十分容易的。这样的设计模式增加的维护代价,收益却不明显。
  • 28、智能指针
    • 构造、析构和赋值容易,注意赋值需要判断是否是自己。operator()和operator->一个返回实际对象,一个返回实际指针
    • 判断存的内容是否为nullptr,调用智能指针的.get(),
    • 与继承的关系,
      • shared_ptr<Base> 和shared_ptr<Derived>不是同一类型,也不能自动转换,所以当函数参数要<Base>时,传<Derived>是不能通过编译的。
        • 对于自己写的函数,可以考虑template,这样会对不同类型的智能指针,产生实例
        • 对于历史的,没有办法改变的函数,考虑做一个拷贝 <Base> b = d,然后传入
    • 与const的关系
      • 虽然const与非const不是一个类型,但是shared_ptr<Base> b; shared_ptr<const Base> cb = b;是可以的,shared_ptr已经内部实现了
    • 个人补充:多线程对同一个智能指针对象操作是不安全的,但是智能指针对象通过复制进入各个线程,则是线程安全的
  • 29、引用计数
    • 什么时候使用引用计数
      • 引用技术是有成本的,当管理的对象越小,其相对来说代价越大。只有当引用技术带来的构造和析构的成本小时,不成为性能瓶颈,且控制数据的申请释放比较困难,我们才更愿意使用(在有限的范围内,数量不是巨大的时候,引用技术是很有意义的)
  • 30、代理类
    • 通过代理类+类型转换,区分operator[]的读和写。以在读和写时进行不同的操作。如智能指针。下面是一个简单的区分原理代码
#include <iostream>

// 原始数据类
class Data {
private:
    int value;

public:
    Data(int val) : value(val) {}

    // 代理类
    class Proxy {
    private:
        Data& data;

    public:
        Proxy(Data& d) : data(d) {}

        // 读操作
        operator int() const {
            std::cout << "Reading value: " << data.value << std::endl;
            return data.value;
        }

        // 写操作
        Proxy& operator=(int newVal) {
            std::cout << "Writing value: " << newVal << std::endl;
            data.value = newVal;
            return *this;
        }
    };

    // 返回代理类对象
    Proxy operator[](int index) {
        // 可以在这里添加索引范围检查等逻辑
        return Proxy(*this);
    }
};

int main() {
    Data dataObj(42);

    // 通过代理类进行读操作
    int readValue = dataObj[0];
    std::cout << "Read result: " << readValue << std::endl;

    // 通过代理类进行写操作
    dataObj[0] = 100;

    return 0;
}

//运行结果
//Reading value: 42
//Read result: 42
//Writing value: 100

作者 : Scott Meyers 译序、导读 : 侯捷 译序(侯捷) C++ 是一个难学易用的语言! C++ 的难学,不仅在其广博的语法,以及语法背後的语意,以及语意背後的深层思维,以及深层思维背後的物件模型;C++ 的难学,还在於它提供了四种不同(但相辅相成)的程式设计思维模式:procedural-based,object-based,object-oriented,generic paradigm。 世上没有白吃的午餐。又要有效率,又要有弹性,又要前瞻望远,又要回溯相容,又要能治大国,又要能烹小鲜,学习起来当然就不可能太简单。 在如此庞大复杂的机制下,万千使用者前仆後续的动力是:一旦学成,妙用无穷。C++ 相关书籍之多,车载斗量;如天上繁星,如过江之鲫。广博如四库全书者有之(The C++ Programming Language、C++ Primer),深奥如重山复水者有之(The Annotated C++ Reference Manual, Inside the C++ Object Model),细说历史者有之(The Design and Evolution of C++, Ruminations on C++),独沽一味者有之(Polymorphism in C++, Genericity in C++),独树一帜者有之(Design Patterns,Large Scale C++ Software Design, C++ FAQs),程式库大全有之(The C++ Standard Library),另辟蹊径者有之(Generic Programming and the STL),工程经验之累积亦有之(Effective C++, More Effective C++, Exceptional C++)。 这其中,「工程经验之累积」对已具C++ 相当基础的程式员而言,有著致命的吸引力与立竿见影的帮助。Scott Meyers 的Effective C++ 和More Effective C++ 是此类佼佼,Herb Sutter 的Exceptional C++ 则是後起之秀。 这类书籍的一个共通特色是轻薄短小,并且高密度地纳入作者浸淫於C++/OOP 领域多年而广泛的经验。它们不但开展读者的视野,也为读者提供各种C++/OOP 常见问题或易犯错误的解决模型。某些小范围主题诸如「在base classes 中使用virtual destructor」、「令operator= 传回*this 的reference」,可能在百科型C++ 语言书籍中亦曾概略提过,但此类书籍以深度探索的方式,让我们了解问题背後的成因、最佳的解法、以及其他可能的牵扯。至於大范围主题,例如smart pointers, reference counting, proxy classes,double dispatching, 基本上已属design patterns 的层级! 这些都是经验的累积和心血的结晶。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值