设为首页 加入收藏

TOP

Effective C++ 简要条款分析(一)
2016-10-08 11:31:15 】 浏览:279
Tags:Effective 简要 条款 分析

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则容易导致多线程安全性问题。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇利用c++实现数值坐标刻度生成,并.. 下一篇【C++研发面试笔记】13. 基本数据..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目