《more effective c++》阅读笔记

2019-10-19  本文已影响0人  镜中无我

基础议题:指针、引用、类型转换、arrays、constructors

条款1:仔细区分指针和引用

当你考虑不指向任何对象或者在不同时间内指向不同对象的可能性时使用指针,而当你确定总是会代表某个确切对象,并且不能再改变指向,则使用引用
另外引用在函数传较大对象的过程中使用较多,在必须返回某种能够作为左值时使
用引用更加清晰,如数组的[]操作符


条款2:最好使用c++转型操作符、

tips:使用新式转型的好处:

  • 严谨语义和易辨识度
  • 编译器容易做转型错误
  • 让转型变得丑陋未尝不是一件好事

条款3:绝对不要以多态的方式处理数组

在条款33中,提出“具体类不要继承自另外一个具体类”


条款4:非必要不提供default constructor

操作符


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

条款6:区分前置式后置式的++,--

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

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

异常


条款9:利用destructors避免内存泄露

条款10:在constructor中避免内存泄漏

条款11:禁止异常流出destructors之外

条款12:了解“抛出一个异常”和“传递一个参数”或“调用一个虚函数”之间的区别

条款13:以by reference方式捕捉exceptions

条款14:明智的使用exception specifications

条款15:了解异常处理的成本

效率

分为两部分:高效的数据结构和算法;高效的语言实现(结合c++语法特性)
条款16:二八法则

条款17:lazy evaluation(对应于eager evaluation)
const string& ConstTest::field1() const{
         ConstTest *const fakeThis=const_cast<ConstTest* const>(this);
         if(fieldValue==0){
             赋值操作;IO操作
         }
}

条款18:分期摊还预期的计算成本(over-eager evaluation)

条款19:临时对象

条款20:返回值优化

条款21:利用重载技术避免隐式类型转化

条款22:考虑以操作符复合形式取代独身形式

条款23:考虑使用其他程序库

条款24:virtual functions、multiple inheritance、base classes、RTTI
  • 虚表机制(编译器提供的服务)在一些场景中应该被回避,如对象的持久化,进程间搬移对象;
  • 动态绑定的调用过程是这样的,首先,基类指针被赋值为派生类对象的地址,那么就可以找到指向这个类的虚函数的隐含指针,然后通过该虚函数的名字就可以在这个虚函数表中找到对应的虚函数的地址。然后进行调用就可以了;
  • 虚函数的内存模型

技术


条款25:将构造器和非成员函数虚化

条款26:限制某个class所能产生的对象数量

条款27:要求或者禁止对象产生于heap之中
//declaration
class HeapTracked{
public:
    class MissingAddress{};
    virtual ~HeapTracked()=0;
    static void *operator new(size_t size);
    static void *operator delete(void *ptr);
    bool isOnHeap() const;
private:
    typedef const void* RawAddress;
    static list<RawAddress> addresses;
};
//definition
void* HeapTracked::operator new(size_t size){
    void *memptr=::operator new(size);
    addresses.push_front(memptr);
    return memptr;
}

void HeapTracked::operator delete(void* ptr){
    list<RawAddress>::iterator it=find(addresses.begin(),addresses.end(),ptr);
    if(it!=addresses.end()){
        addresses.erase(it);
        ::operator delete(ptr);
    }else{
        throw MissingAddress();
    }

}

bool HeapTracked::isOnHeap() const{
    const void *rawAddress=dynamic_cast<const void*>(this);
    list<RawAddress>::iterator it=find(addresses.begin(),addresses.end(),rawAddress);
    return it!=addresses.end();
}

// how to use it

class Asset: public HeapTracked{
    

};

void inventoryAsset(const Asset *ap){
    if(ap->isOnHeap()){}
    else{}
}
条款28:智能指针
  1. 构造和析构,通过引用计数管理资源
  2. 复制和赋值,你可以决定深复制还是浅复制等
  3. 解引用,你可以决定解引用时发生的行为,比如lazy fetching

nvmf是一项先进的技术,它涵盖了以下四种重量级的要素:

  • 函数调用的自变量匹配规则
  • 隐式类型转换函数
  • template functions的暗自实例化
  • 成员函数模板等技术
\\base class
template<class T>
class SmartPtrToConst{
...
functions
...
protected:
union{
      const T* constPointee;
      T* pointee;
}
}
\\ derived class
template<class T>
class SmartPtr: public SmartPtrToConst{
没有data members
}

条款29:引用计数
//declarations

class String{
    public:
        String(const char* initValue="");
        String(const String&);
        String& operator=(const String&);
        ~String();
        const char& operator[]const(int);
        char& operator[](int);
        
    private:
        struct StringValue{
            int refCount;
            char* data;
            StringValue(const char*);
            ~StringValue();
            }
        StringValue* value;
};

//definitions for StringValue

String::StringValue::StringValue(const char * initValue):refCount(1){
    
    data=new char[strlen(initValue)+1];
    strcpy(data,initValue);

}

String::StringValue::~StringValue(){
    delete [] data;
}
//definitions for String
String::String(const char * initValue = ""):value(new StringValue(initValue)){//这里本也可以实现真正的按字面量共享爱过你,但是
//太过精细反而会降低效率
}
String::String(const String & rhs):value(rhs.value){
    ++value->refCount;
}
String& operator=(const String& rhs){
    if(value==rhs.value){//这个并不是按照字符串值进行比较,而是按照已创建对象之间是否有共有关系
        return *this
    }
    if(--value->refCount==0) {
        delete value;//检查当前对象状态,如果计数为零则当前指针有责任释放
    }
    value=rhs.value;//指向新的对象,并更新对象的引用计数
    ++value->refCount++;
    return *this;
}
String::~String(){
    if(--value->refCount==0) delete value;
}
const char& String::operator[](int index) const{
    return value->data[index]; 
}
//区分写操作和读操作(copy-on-write)
char& String::operator[](int index){
    if(value->refCount>1) {
        --value->refCount;
        value=new StringValue(value->data);
    }
    return value->data[index];
}

  • RCObect的赋值操作从来都不会发生,因为在一个有reference counting的系统中,实值对象并不会被赋值,只会改变引用次数;
  • 这里的实值对象是专属于应用对象的,所以将其定义为嵌套类,而让一个嵌套类继承自另一个类,而后者与外围类无关,这种继承关系将越来越司空见惯;
  • 如何自动操作(引用计数):使用模板智能指针而不是常规指针来操作实值对象,而关于写时复制,引用计数(构造、赋值、析构),深度复制等操作都被其自动处理。
  • 但是在以下语句时需要注意:
    pointee=new T(*pointee);
    当你使用智能指针的init产生副本时,它会调用实值对象的copy constructor,而我们知道默认的copy constructor只会对成员指针执行浅复制,所以我们需要为所有内含指针的类重写copy constructor。
  • 对于应用对象,我们不用单独重写copy 和assignment,因为默认的就足够了

什么时候适合用引用计数 ?

  1. 考虑其成本:计数器的创建和更新(CPU)、额外分配的内存(memory)、复杂的底层开发(开发成本)
  2. 适用于对象常常共享实值的情况:相对多数的对象共享相对少量的实值;对象实值创建和销毁的成本较高(内存和CPU)
  3. 其他考虑:自我引用和循环依赖的数据结构可能会导致对象的引用次数总是大于零;“不确定谁被允许删除什么东西”的问题也可以通过引用计数解决,而这个通常是许多程序员入坑的主要因素。
  4. 实值对象为new所得: 这和智能指针的要求是一样的,而应用对象负有确知实值对象共享性并将其实例化的责任。

条款30:proxy classes(替身类和代理类)

读-->右值引用-->operator[]返回代理对象-->赋值操作operator[]
写-->左值引用-->operator[]返回代理对象-->其他方式使用如隐式类型转化char()
具体实现如下:

class String{
public:
    ...
    class CharProxy{
    public:
        CharProxy(String& str,int index);
        CharProxy& operator=(const CharProxy& rhs);
        CharProxy& operator=(char c);
        operator char() const;
    private:
        String& theString;
        int charIndex;

    }
    const CharProxy operator[](int index)const;
    CharProxy operator[](int index);
    ...
friend class CharProxy;
private:
    RCPtr<StringValue> value;
    ...
};
//使用代理对象延缓请求
const String::CharProxy String::operator[](int index)const{
    return CharProxy(const_cast<string&>(*this),index);
}
String::CharProxy String::operator[](int index){
    return CharProxy(*this,index);
}
//根据具体操作决定使用哪种方式访问
String::CharProxy::CharProxy(String& str,int index):theString(str),charIndex(index){}
String::CharProxy::operator char() const{
    return theString.value->data[charIndex];
}
String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs){
    if(theString.value->isShared()){
        theString.value=new StringValue(theString.value->data);//智能指针的隐式构造
    }
    thsString.value->data[charIndex]=rhs.theString.value->data[rhs.charIndex];
    return theString.value->data[rhs.charIndex];
}


条款31:让函数根据一个以上的对象类型来决定如何虚化
class GameObject{
public:
    virtual void collide(GameObject& OtherObject)=0;

};

class SpaceShip:public GameObject{
public:
    virtual void collide(GameObject& otherObject){
        processCollision(otherOject,*this);
    }
private:
    static void processCollision(GameObject& o1,GameObject& o2){
        lookup(typeid(o1).name()),typeid(o2).name())
    }
};

namespace{
   void ShipStation(GameObject&){
    // the process for collision;
    }
   void StaionShip(GameObject& g1,GameObject& g2){
        return ShipStation(g2,g1);
   }
   typedef void (*HitFuncPtr)(GameObject&,GameObject&);
   typedef map<pair<string,string>,HitFuncPtr> HitMap;
   HitMap* initHitMap();
   HitFuncPtr lookup(HitMap* m);

}
//封装
class CollisionMap{
public:
    void ShipStation(GameObject&){
     // the process for collision;
     }
    void StaionShip(GameObject& g1,GameObject& g2){
         return ShipStation(g2,g1);
    }
    void addEntry(const string& class1,const string& class2, HitFuncPtr HitFunc,bool symmetric=true);
        void remove(const string& class1,const string& class2); 
    HitMap* initHitMap();
    void processCollision(GameObject& o1,GameObject& o2){
        HitFuncPtr HitFunc= lookup(typeid(o1).name()),typeid(o2).name());
        (*HitFunc)(o1,o2);
    }
private:
    HitFuncPtr lookup(HitMap* m);
    typedef void (*HitFuncPtr)(GameObject&,GameObject&);
    typedef map<pair<string,string>,HitFuncPtr> HitMap;
};

  1. 匿名namespace表明其内容为该文件私有(类似于static的功能)
  2. 对称的碰撞函数
  3. lookup里面对于map的定义使用static的智能指针,便于回收;
  4. 这里的CollisionMap的类应该是单例的
  5. 数组和指针类型的判断:查看定义式中表示的单个元素的意义,比如p[10]表示单个指针指向的内容,所以它是一个指针数组,如果是这样p,它则表示一个指向指针的指针,可以用以表示二维动态数组,(p)[10]此时指每一个指针都指向一个包含十个元素的数组;

杂项讨论

条款32:在未来时态下发展程序

条款33:将非尾端类设计成抽象类

可以理解此处的继承关系保存了封装,复用等特性,牺牲了多态的特性

将需要封装的接口放到protected里面,比如赋值运算符重载,可以避免多态数组的问题


条款34:如何在同一个程序中结合c和c++
名称重整
//几种实现语法:
extern "C" void myfunc();
extern "C"{
     void myfunc1();
     void myfunc2();
......
}
//被c和c++同时使用的语法,考虑
#ifdef _cplusplus
extern "C"{
#endif
....
#ifdef _cplusplus
}
#endif
statics的初始化
动态内存分配
数据结构的兼容性

条款35: 让自己习惯于标准c++
上一篇下一篇

猜你喜欢

热点阅读