欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-01-22 19:48:04
c++实在是一门深奥晦涩的语言,不同专业水准的程序员写出来的代码质量有着天壤之别,以至于必须出版一本提供一些“专家经验”来引导c++程序员写出更加高质量的代码。 为驳回编译...

c++实在是一门深奥晦涩的语言,不同专业水准的程序员写出来的代码质量有着天壤之别,以至于必须出版一本提供一些“专家经验”来引导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    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则容易导致多线程安全性问题。