effective c++学习笔记

Effective C++

7.1C++不同次语言,条款不同

C++有四个次语言:C,object-oriented C++,template C++,STL。不同的次语言其高效规则不同。例如对于内置类型(C like)而言,值传递要比引用传递高效;但是在object-oriented C++中由于用户自定义的构造函数和析构函数的存在,传递常量引用的方式更好,template C++中也是这样,因为彼时你甚至不知道所处理的对象类型;然而在STL中,迭代器和函数对象都是在C指针上塑造出来的,此时值传递再次适用。

7.2尽量用const,enum,inline代替#define

1.因为使用#define RATIO 1.65时,记号名称RATIO也许从未被编译器看见,也许在编译器处理之前就已经被预处理器移走了,此时该记号名称有可能没有进入记号表(symbol table)中,此时会导致错误。

使用const double Ratio = 1.65;

此时作为一个语言常量,该变量肯定会被编译器看到,当然会进入记号表内。此外对于浮点常量,使用const可能会比#define导致较小量的目标码,因为预处理器会盲目地将宏定义RATIO替换为1.65,导致目标码中出现多份1.65,而const则不会。

 

2.当定义常量指针时,由于常量定义式通常被放在头文件内(便于被不同源码含入),因此有必要将其指针声明为常量:

const char* const Name = "scott";(然而实际上使用string更好)。

 

3.当定义class的专属常量时,为了将常量的作用域限定在class内,必须让它成为class的成员,为了确保此常量至多只有一份实体,必须定义为static:

class Player{

private:

    static const int num = 5;//常量声明式

    int scores[num];//使用该常量

    ...

};

上述为声明式而非定义式。

定义式:const int Player::num;//由于声明式中已经设置初值,此处可不写。并且此定义式应当放在实现文件中而非头文件中。

#define则不能创建class专属常量。因为它不重视作用域,一旦定义,其后都能使用。

编译器必须在编译期间明确知道数组的大小,因此上述类中num必须得是常量且明确。万一编译器错误地不允许static整数类型class常量 完成 in-class初值设定,则需改为其他方法(如 the enum hack)。

 

4.使用template inline函数代替#define 宏。

7.3尽可能使用const(use const whenever possible)

1.如果想要在const成员函数中修改变量的值,则需要将这些变量定义为mutable的。

 

2.在const和non-const成员函数中尽量避免重复。

同时需要注意不能令const版本的成员函数调用non-const的成员函数,因为const成员函数承诺绝不改变对象的逻辑状态,而non-const成员函数并没有这样的保证。因此使用static_cast等来模仿上述操作是不恰当的,存在风险的行为。

7.4确定对象被使用之前已经被初始化了

1.永远在使用对象前进行初始化。

2.对于内置类型之外的任何东西:确保每一个构造函数都将对象的每一个成员初始化。

3.区分赋值和初始化:

如果成员变量在成员初始化列表中没有被指定初始值,则编译器会为其自动调用默认构造函数。最好将所有需要初始化的成员变量都用成员初始化列表的方式初始化。

4.成员初始化顺序:基类早于派生类,同一个类中成员变量总是以其声明的次序被初始化,而不是以成员初始化列表中的次序。

5.static对象的寿命从被构造出来直到main()结束为止,因此stack和heap-based对象都被排除。C++对“定义于不同编译单元内的non-local-static对象”的初始化次序没有明确定义。此时如果某编译单元内一个non-local-static对象的初始化需要使用另一个编译单元的某non-local-static对象,则会出现问题(编译单元:单一源码加上其头文件)(local-static:函数内的static对象)。解决办法:将每个non-local-static对象搬到自己专属的函数内,声明为static,此时变成了local-static。这些函数返回一个引用指向它所含的对象。然后用户直接调用这些函数而不是这些对象。原理:C++保证函数内的local static对象会在该函数被调用期间首次遇到该对象的定义式时被初始化。

7.5了解C++默默编写并调用哪些函数

因为C++不允许“让reference改指向不同的对象”,而上述class中nameValue是一个string的引用,并且该类中没有定义copy assignment 赋值拷贝操作符,因此对于p=s这个操作编译器给出的响应式报错。同样地,由于const的类型变量不能修改,因此编译器自己生成赋值拷贝操作符时不知该如何操作,因此会报错。

此外,如果某个基类将赋值拷贝操作符声明为了private的,编译器将拒绝为其派生类生成一个赋值拷贝操作符。因为编译器为派生类生成的copy assignment想象中可以处理基类的成分,但它们当然无权调用派生类无权调用的函数,此时编译器无能为力,从而报错。

7.6若不想使用编译器自动生成的函数,就应当明确拒绝

例如希望你写的类不允许进行拷贝和赋值操作,只能有一个对象时,假如不给类定义拷贝操作符和拷贝构造函数,则编译器会自动为你声明。解决办法:由于编译器产出的函数都是public的,为阻止这些函数被创建,可以自行将拷贝操作符和拷贝构造函数声明为private。即“将成员函数声明为private并且故意不实现它们”。

还有一种做法可以将上述链接期间的错误转移到编译期间。

此外该uncopyable基类不一定得是通过public来继承,其析构函数不一定得是virtual的。通常这个方式还会导致多重继承,因为还需要继承其他的类。

7.7为多态基类声明virtual析构函数

1.polymorphic(带多态性质的)基类应该声明一个virtual析构函数。如果一个class带有任何virtual函数,它就应该拥有一个virtual析构函数。例如:

此时因为工厂函数返回的指针指向的是TimeKeeper派生类动态分配的对象,而这个对象却经由一个基类指针ptk删除,并且此例子中基类的析构函数不是虚析构函数。这种情况下实际执行时,对象的派生成分没有被销毁,因为派生类的析构函数没有执行;而其基类的成分被销毁了,从而造成“局部销毁”的对象,进而导致内存泄漏等等问题。

解决方法:给基类一个虚析构函数。此时上述操作就会执行派生类的析构函数,从而将整个对象正常销毁掉。

2.如果一个类不是用来作为基类使用,或者这个基类不是为了具备多态性,那么就不应该声明虚析构函数。对于前者而言,将一个不用做基类的类声明虚析构函数会导致虚函数表和虚函数指针的出现,它们会增加占用空间,指针在32位系统上占4字节,64位系统上占8字节,即凭空多占用了4/8个字节。并且还会有向C移植性变差等问题;对于后者来说,例如std::string和STL都不被用作基类,更别提多态了。此外现在C++11已经支持了final关键字,因此可以防止错误地继承这些不被用作基类的类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值