Effective C++ 总结

来源:互联网 发布:java jdbc 连接 编辑:程序博客网 时间:2024/06/05 16:33

第三版

Rule 3: 尽可能的使用const

  • const可以作用于作用于内的对象、函数参数、函数返回值、成员函数体
  • 可以使用const进行函数重载
  • 对“成员函数为const”的两种观点:bitwise constness 和 logical constness
  • bitwise constness: const成员函数绝不改变任何成员变量(static除外)时才可以说时const。C++对“常量性”的定义就是如此。(但是,想想一个类种有指针成员变量的情况,char * text,有可能成员函数没有改变指针本身的值,但是指针指向内容确可以被意外改变,详见书中的例子)
  • logical constness:const成员函数可以改变对象的成员变量,但是不能让客户端侦测出来
  • mutable(可变的) 能够释放掉non-static成员变量的bitwise constness约束,使得该成员变量可以在const成员函数中被修改。
  • 在const和non-const成员函数中避免重复(避免代码重复等问题),当const和non-const成员函数有实质等价的实现时,令non-const版本调用const版本可以避免代码重复。

Rule 4: 确定对象被使用前已经被初始化

  • 注意初始化和赋值之间的区别!初始化位于构造函数的函数体之前的部分,称为构造函数的初始化列表(此处进行初始化),构造函数的函数体内使用“=”为赋值。初始化列表更高效!
  • c++对“定义于不同的编译单元内的non-local static对象”的初始化次序没有明确定义。使用 local static对象代替non-local static 对象。
  • 对内置类型对象,要手工初始化,因为c++不保证初始化它们。
  • 构造函数最好使用成员初始化列表,而不要在构造函数内部使用赋值操作(assignment)。初始化列表中成员的顺序要与class中声明次序保持一致(良好的习惯)。
  • copy constructor : 拷贝构造函数。会在以下三种情况下被调用:
    1.一个对象以值传递的方式传入函数体
    2.一个对象以值传递的方式从函数返回
    3.一个对象需要通过另外一个对象进行初始化。
    A aa(a); 或 A aa=a; 都会调用拷贝构造函数。注意A aa=a; 不是调用 copy assignment(operator=) ,原因是这是个定义,不是个赋值。但是如果是A aa; aa=a;,第二个语句会调用operator=,这里是赋值。
//Singleton 模式实现的一种方法static CApplication & Instance(){    // local static,函数第一次被调用时构造。以后的调用直接返回。实现了单例模式。    static CApplication appliction;     return application;//返回local static的引用}

注意local-static的线程安全性。C++11能确保local-static的线程安全性。此外,使用pthread_once也可以实现线程安全的Singleton模式。

Rule 20: Prefer to Pass-by-const-reference to Pass-by-value

参数传递时,使用const reference 代替 pass-by-value(值传递)。

值传递的缺点:

  1. 额外的构造和析构
    包括对象本身以及对象的类类型成员变量,如果是派生类,那么基类的构造析构也会调用。

  2. 对象切割问题(slicing)
    如果使用pass-by-value传递参数,但把一个派生类对象传递给基类类型参数时,会发生对象切割,只有基类的部分被正确传递。这是需要使用引用。(引用在底层是用指针来实现的,对于内置类型、STL迭代器、函数对象,使用pass-value比较合理。)

此外,C++ FAQ 指出,return-by-value经过编译器的优化,不会有额外的copy等开销。

Rule 25: Consider support non-throwing swap

std::swap用于交换两个对象值,只要类型T支持Copying (copying ctor 和 Copy assign)即可。实现细节类似这样:

T tmp(a);a = b;b = a;

进行了一次copy ctor,和两次copy assign。

这种行为在某些场景下比较低效。例如使用“pimpl”手法设计的类(pointer to implementation),通常我们只是希望交换指针的内容而已。但是如果使用缺省的std::swap却会进行一些不必要的操作(注意Widget::operator=, 他会调用WidgetImpl的operator=, 这会复制它的所有成员,包括两个int和一个vector,非常低效!)

文中给出了第一个解决办法:将std::swap针对Widget特化:

class WidgetImpl {    friend ostream& operator<< (ostream& os, const WidgetImpl&);public:    WidgetImpl(int a, int b) : a_(a), b_(b) { v_.push_back(a); v_.push_back(b); }private:    int a_, b_;    std::vector<double> v_;};class Widget {    friend ostream& operator<< (ostream& os, const Widget& w);    friend void swap<Widget>(Widget&, Widget&); // 声明特化版本的swap为友元函数public:    Widget(WidgetImpl* p) : pimpl_(p) {}    Widget& operator= (const Widget& rhs)    {        std::cout << "operator=" << std::endl;        *pimpl_ = *rhs.pimpl_;        return *this;    }private:    WidgetImpl *pimpl_;};// 可以对std内的模板函数进行特化namespace std {    template<> // 表示这是一个全特化    void swap<Widget>(Widget& a,  // <Widget>表示针对Widget进行特化                      Widget& b)    {        swap(a.pimpl_, b.pimpl_); // 需要访问Widget的私有成员,因此在上面声明为friend函数。    }}ostream& operator<< (ostream& os, const WidgetImpl& w){    os << w.a_ << " " <<  w.b_ << std::endl;    return os;}ostream& operator<< (ostream& os, const Widget& w){    os << *w.pimpl_ << std::endl;    return os;}int main(){    WidgetImpl w1(1,2), w2(3,4);    //std::cout << w1 << w2;    Widget pw1(&w1), pw2(&w2);    swap(pw1, pw2);    std::cout << pw1 << pw2;    return 0;}

第二种方法,在特化版本的swap中调用Widget的成员函数,这样无需声明friend函数。改动如下所示:

void Widget::swap(Widget& rhs){    using std::swap;    swap(pimpl_, rhs.pimpl_);}namespace std {    template<>    void swap<Widget>(Widget& a,                       Widget& b)    {        //swap(a.pimpl_, b.pimpl_);        a.swap(b);    }}

这样的做法与STL容器有一致性,所有STL容器也提供有public swap函数,和std::swap特化版本(用来调用前面的public swap函数)。

Rule 30: Understand “inline”

  • inline 只是对编译器的一个申请,不是强制命令。它可以被显示地提出(使用inline关键字),也可以隐喻地提出(将函数定义写在class内)。

Rule 42: Understand two meanings of typename

  • 声明template参数时,关键字typenameclass 等价。
  • 应该使用typename标识嵌套从属类型名称;但是不得在 base class list 和 member initialize list 内以typename作为base class的修饰符。
template<typename Container>void print(const Container& c){    typename Container::const_iterator cit;  // const_iterator 是个嵌套从属类型名称,需要在前面加上typename    cit = c.begin();    std::cout << *cit << std::endl;}
0 0
原创粉丝点击