Effective C++ 简要条款分析(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/NK_test/article/details/52717784

Effective C++ 简要条款分析(一)

c++实在是一门深奥晦涩的语言,不同专业水准的程序员写出来的代码质量有着天壤之别,以至于必须出版一本图书提供一些“专家经验”来引导c++程序员写出更加高质量的代码。《Effective C++》就是这样一本书。

建议你在有一定的代码积累后阅读这本书,这里我总结一些我读完有感触的条款,和大家一起分析。


为驳回编译器(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

一般来说,如果我们不希望class提供某些功能,只需要不去定义相应的函数即可。但是这个策略对copy构造函数和copy assignment操作符不起作用,因为如果你不声明,编译器会为你声明它们。那么如果禁止拷贝对象呢?这个问题解决的关键在于所有编译器产出的函数都是public,因此我们只需要把这些函数声明为private 便可解决禁止copying 的问题。
这里有两个细节需要注意:为了防止member 函数和friend 函数调用private 函数,我们只声明不定义;为了将连接期错误转移至编译期,只需要定义如下的base class ,然后继承即可。

class Uncopyable{
protected:
    Uncopyable(){} //允许derived 对象构造和析构 
    ~Uncopyable(){}
private:
    Uncopyable(const Uncopyable&);//但阻止copying 
    Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale: private Uncopyable{
    ... //不再声明copy构造函数或者copy assign.操作符 
};
class HomeForSale: public boost::noncopyable{
    ... //使用boost库的noncopyable也可 
}

polymorphic(带多态性质的)base classes 应该声明一个virtual 析构函数。如果class 带有任何virtual 函数,他就应该拥有一个virtual 析构函数。但是如果类的设计并不是作为base classes 使用,或不是为了具备多态性,那就不该声明virtual 函数。

c++明确指出,当derived class 对象经由一个base class 指针被删除,而该base class 带着一个non-virtual 析构函数,其结果未有定义-实际执行时通常发生的是对象的derived 部分没被销毁。
消除这个问题的方法很简单,就是给base class 一个virtual析构函数。此后删除derived class 对象就会如你所想的那样,正常销毁。而virtual 函数的实现机制是通过虚函数表,会导致对象的体积增大,所以在非base class 中使用virtual 是一个馊主意。

普遍而常见的 RAII class copying行为是:抑制copying、施行引用计数法。另外shared_ptr可以定制删除器。

在底层资源管理中,我们祭出“引用计数法”:保有资源,直到最后一个使用者(对象)被销毁。shared_ptr可以轻松实现这种引用计数,但是它的缺省行为是“当引用计数为0时删除其所指物”,有时候这并不是我们想要的行为。幸运的是shared_ptr允许指定所谓的删除器,在引用计数为0时调用,例子如下:

  //定置删除器的仿函数  
struct Fclose  
{  
       void operator()(void *ptr)  
       {  
              fclose((FILE *)ptr);  
              cout << "fclose()" << endl;  
       }  
};  
void test()  
{  
       //调用构造函数构造一个匿名对象传递过去,文件正常关闭 
       boost::shared_ptr<FILE>    sp(fopen("test.txt","w"),Fclose());     

}  

以上就实现了通过定制的删除器对文件资源的管理,也正好说明了shared_ptr并不仅仅局限于对内存这种资源的管理。另一方面,shared_ptr通过定制删除器也可以防范DLL问题,可被用来自动解除互斥锁。
另备注一点:shared_ptr通过可以通过get方法获取到raw pointer,这适用于某些对参数有限制的函数。

尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题。但是以上规则并不适用于内置类型,以及STL的迭代器和对象,对它们而言,pass-by-value往往比较适当。

先说效率问题,pass-by-refer-to-const没有构造函数或析构函数被调用,因为没有新的对象被创建,而pass-by-value则需要多次拷贝构造函数和析构函数。
关于切割问题,如下:

class Window {
public:
  string name() const;             // 返回窗口名
  virtual void display() const;    // 绘制窗口内容
};
class WindowWithScrollBars: public Window {
public:
  virtual void display() const;
};

// 一个受“切割问题”困扰的函数
void printNameAndDisplay(Window w)
{
  cout << w.name();
  w.display();
}

想象当用一个WindowWithScrollBars对象来调用这个函数时将发生什么:

WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

参数w将会作为一个Windows对象而被创建(它是通过值来传递的,记得吗?),所有wwsb所具有的作为WindowWithScrollBars对象的行为特性都被“切割”掉了。printNameAndDisplay内部,w的行为就象是一个类Window的对象(因为它本身就是一个Window的对象),而不管当初传到函数的对象类型是什么。尤其是printNameAndDisplay内部对display的调用总是Window::display,而不是WindowWithScrollBars::display

解决的方法就是使用pass-by-ref-to-const来传递w,因为pass-by-ref通常意味着传递的是指针。

// 一个不受“切割问题”困扰的函数
void printNameAndDisplay(const Window& w)
{
  cout << w.name();
  w.display();
}

至于内置类型和STL的迭代器和函数对象,一般他们都被设计为pass-by-value,效率往往更高一些,这只是一个建议。

绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能需要多个这样的对象。

一旦你领悟了pass-by-value在效率方面的牵连,往往一心一意根除pass-by-value带来的种种邪恶,在这个过程中,有可能会产生一些致命错误!就像上面条款提到的。
条款中对operator *()返回&导致错误的例子这里不再提及。对于返回local stack对象的引用,很明显,在函数退出前,对象被销毁,这会导致“未定义行为”。对于heap-allocated对象,则因为需要额外的delete,很可能导致内存泄露。static则容易导致多线程安全性问题。


参考
《Effective C++中文第三版》

展开阅读全文

没有更多推荐了,返回首页