`
evasiu
  • 浏览: 165477 次
  • 性别: Icon_minigender_2
  • 来自: 广州
博客专栏
Fa47b089-e026-399c-b770-017349f619d5
TCP/IP详解卷一>阅读...
浏览量:12264
社区版块
存档分类
最新评论

effective c++ -- 实现

 
阅读更多
这一章叙述了实现过程中应该考虑的一些问题,例如,太快定义变量可能造成效率上的拖延;过度使用转型可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象“对象内部数据之handle”可能会封装并留给用户虚吊handle;未考虑异常带来的冲击则可能导致资源泄露和数据败坏;过度热心地inlining可能引起代码膨胀;过度耦合则可能导致让人不满意的冗长建置时间。

Item 26: 尽可能延后变量定义式的出现时间
只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序的控制流到达这个变量定义式时,你便得承受构造成本;当这个变量离开其作用域时,你便得承受析构成本,即使从头到尾你都没有使用过这个变量,因此,应该尽可以避免这种情况。这包含两层深意:
(1)在需要该变量时方定义它;
(2)应该尝试延后这份定义直到能够给它初始为止
这样不仅能够避免构造和析构非必要对象,还可以避免无意义的default构造行为。
对于循环,书中的建议是,除非(1)你知道赋值成本比“构造+析构”成本低;(2)正在处理的代码中效率高度敏感。否则,应该选择n个构造+n个析构。

Item 27: 尽量少做转型动作
C风格的转型动作:
(Type) expression	//将expression转型为Type
Type (expression)	//将expression转型为Type

C++提供的四种新式转型:
(1)const_cast<T>( expression ):将对象的常量性转除,它也是唯一有此能力的转型操作符。我们在提到非const成员函数利用const成员函数的时候提到过的。
(2)dynamic_cast<T>( expression ):安全向下转型,只用于对象的引用和指针。当用于多态时,它允许任意的隐式类型转换及相反的过程,在后一种情况中,dynamic_cast会检查操作是否有效,它会检查转换是否会返回一个被请求的有效的完整对象。检测在运行时进行,如果被转换的指针不是一个被请求的有效完整的对象指针,则返回null。
如果一个引用类型执行了类型转换并且这个转换是不可行的,一个bad_cast异常会被抛出。如下:
class Base{
  public:
    ...
      virtual dummy(){}
    ...
};

class Derived: public Base{}
Base* b1 = new Base();
Base* b2 = new Derived();

Derived* pd1 = dynamic_cast<Derived *>( b1 );	//fails: return NULL
Derived* pd2 = dynamic_cast<Derived *>( b2 );	//succeeds!

Derived d1 = dynamic_cast<Derived&>( *b1 );	//fail: bad_cast is thrown
Derived d2 = dynamic_cast<Derived&>( *b2 );	//succeeds!

(3)static_cast<T>( expression ) 用来强迫隐式转换,例如将non-const对象转为const对象,或将int转为double等等。它也可以用来执行上述多种转换的反向转换,例如将void*指针转为typed指针,将pointer-to-base转为pointer-to-derived。但它无法将const转换为non-const——这只有const_cast可以办到。
(4)reinterpret_cast<T>( expression ):转换一个指针为其他类型的指针,也允许从一个指针转换成整数类型。这个操作符能够在非相关的类型之间转换,操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间的指向的内容不做任何类型的检查和转换。如果情况是从一个指针到整型的拷贝,内容的解释是系统相关的,所以不具备移植性。
转型可能能代码运行效率下降,而且不容易维护:
(1)如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
(2)如果转型是必要的,试着将它隐藏于某个函数背后,客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。
(3)宁可使用C++ style转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。

Item 28: 避免返回handles指向对象内部成分
所谓“返回handles指向对象内部成分”是下面这样一种情况:
class Point{
  public:
    Point( int x=0, int y=0 );
    void setX( int x );
    void setY( int y );
  private:
    int xPos;
    int yPos;
};

struct RectData{
  Point ulhc;
  Point lrhc;
};

class Rectangle{
  public:
    Rectangle( Point ul, Point lr ){
      pData = new RectData();
      pData->ulhc = ul;
      pData->lrhc = lr;
    }

    Point& upperLeft() const{
      return pData->ulhc;
    }
    Point& lowRight() const{
      return pData->lrhc;
    }
  private:
    RectData* pData;
};

upperLeft和lowRight返回了“代表对象内部”的handles,其问题在于:
(1)一方面upperLeft和lowRight被声明为const成员函数,因为它们的目的只是为了提供客户一个得知Rectangle相关坐标点的方法,而不是让客户修改Rectangle;
(2)两个函数都返回reference指向private内部数据,调用者于是可以通过这些reference更改内部数据:
int main(){
  Point coord1( 0, 0 );
  Point coord2( 100, 100 );
  const Rectangle rec( coord1, coord2 );
  rec.upperLeft().setX( 50 );	//coord1( 50, 0 ), coord2( 100, 100 );
}

这给我们两个启示:
(1)成员变量的封装性最多只等于“返回其reference”的函数的访问级别;
(2)如果const成员函数传出一个reference,后者所指数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。如果我们对该reference添加const限制,才能实现const成员函数的承诺。
但是即使如此,“返回对象内部handles”的做法还是会有其他问题,它可能导致dangling handles:
class GUIObject{}
const Rectangle	boundingBox( const GUIObject& obj );	//返回一个矩形
//用户可能这么使用函数:
GUIObject* pgo;
...
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
//语句结束后,boundingBox(*pgo)返回的临时变量被销毁,包括其内部对象,
//于是pUpperLeft就变成dangling handles了。


Item 29: 为“异常安全”而努力是值得的
当异常被抛出时,带有异常安全性的函数会:
(1)不泄漏任何资源(通常通过对象管理类来解决);
(2)不允许数据败坏。
异常安全函数提供以下三个保证之一:
(1)基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下(如所有的class约束条件都继续获得满足),然而程序的现实状态并不确定。
(2)强烈保证:如果异常被抛出,程序状态不改变。强烈保证通常通过copy-and-swap来实现。
(3)不抛出异常:承诺绝不抛出异常。

Item 30:透彻了解inlining的里里外外
Inline函数看起来像函数,动作像函数,但比宏好得多,可以调用它们又不需要蒙受函数调用所招致的额外开销。Inline函数背后的整体观念是,将“对些函数的每一个调用”都以函数本体替换之,其带来的负面影响是,可能增加目标码的大小。
Inline只是对编译器的一个申请,不是强制命令。这个申请可以隐喻提出,也可以明确提出。
(1)隐喻方式是将函数定义于class定义式内:
class Person{
  public:
    //隐喻的inline函数
    int age() const {	return theAge; }
    ...
  private:
      int theAge;
};

(2)明确声明inline函数的做法是在其定义式前加上关键字inline。
Inline函数通常一定被置于头文件内,因为大多数build environments在编译过程中进行inlining,而为了将一个“函数调用”替换为“被调用函数的本体”,编译器必须知道那个函数长什么样子。虽然有些build environments可以在运行期或连接期完成inlining,但是Inlining在大多数C++程序中是编译行为。
(3)至于“Inline只是对编译器的一个申请,不是强制命令”,大部分编译器拒绝将太过复杂(如带有循环或递归)的函数inlining,而所有对virtual函数的调用(除非是最平淡无奇的)也都会使inlining落空。
有时候虽然编译器有意愿inlining某个函数,但还是可能为该函数生成一个函数本体。例如程序要取某一inline函数的地址,编译器通常必须为此函数生成一个oulined函数本体。
(4)构造函数和析构函数往往是Inlining的糟糕候选人。因为C++对于“对象被创建和销毁时发生什么事”做了各式各样的保证。当你使用new,动态创建的对象被其构造函数自动初始化;当你使用delete,对应的析构函数会被调用。当你创建一个对象,其每一个base class及每一个成员变量都会被自动构造;当你销毁一个对象,反向程序的析构行为亦会自动发生。如果有个异常在对象构造期间被抛出,该对象已构造好的那个一部分会被自动销毁。因此,看起来为空的一个构造函数,编译器可能会它自动产生了一些代码以实现这些保证,而做为inline函数,这些代码将被替换到创建一个新对象的地方,从而造成代码的膨胀。
(5)template通常也被置于头文件中,因为它一旦被使用,编译器为了将它具现化,需要知道它长什么样子。但template的具现化与inline函数无关。

Item 31: 将文件间的编译依存关系降至最低
我不是很确定我正确地理解了这一节的内容。关于消除依存关系的,书中举了个例子,如下:
class Person{
  public:
    Person( const std::string&, const Date& birthday, const Address& addr);
    std::string name() const;
    std::string birthday() const;
    std::string address() const;
  private:
    std::string theName;
    Date theBirth;
    Address theAddr;
};

这是Person的.h文件,为了成功地编译出Person.o文件,Person.cpp文件中必须包含以下头文件:
#include<string>
#include "Date.h"
#include "Address.h"

这样一来,假如Date或Address有变动而需要重新编译,或者Person的实现有小改动,Person就得重新编译,所有使用Person的编译单元都要重新编译,这样一连串下去,将会造成巨大的编译消耗。
怎样才能更好地把“接口与实现分离”开呢?书中介绍了两种方法,一种是pointer to implementation,也就是用一个PersonImpl做Person真正做的事,而Person只含有一个PersonImpl的指针,所有的接口都通过该指针指向PersonImpl的成员函数。这样,Person的接口其与定义的完全分离开了。书中说的是,“Person的用户就只需要在Person接口被修改过时才重新编译。”这让人想起java中的RMI,客户端只需要一份Person的接口说明文件,如果服务器端的实现代码发生了变化,客户端的代码也不需要重新编译。如果接口说明文件用的是上面的那个版本,那么客户端还要知道Date.h以及Address.h中的定义。
新的Person.h如下:
#include<string>
#include<tr1/memory>

class Date;
class Address;
class PersonImpl;

class Person{
  public:
    Person( const std::string& name, const Date& birthday, const Address& addr);
    std::string name() const;
    std::string birthday() const;
    std::string address() const;
  private:
    std::tr1::shared_ptr<PersonImpl> pImpl;
};

而所有的函数func()的实现都是通过pImpl->func()实现的。
另一种制作Handle class的办法是,令Person成为一种特殊的abstract base class,称为Interface class。如下:
class Date;
class Address;
class Person{
  public:
    virtual ~Person();
    virtual std::string name() const = 0;
    virtual std::string birthDate() const = 0;
    virtual std::string address() const = 0;
    static std::tr1::shared_ptr<Person> create( const std::string& name, const Date& birthday, const Address& addr );
};

总结起来,反正就是让Person.h包含的头文件越少越好,从而减少了依赖关系。我觉得我还得多了解一下c++的编译连接过程,那个所谓的使用Person的客户使用Person的方式,或许是我所没有了解到的。
分享到:
评论

相关推荐

    Effective C++.mobi kindle可用

    Effective C++.mobi kindle可用 学习一种编程语言的基础是一回事;学习如何用那种语言设计和实现高效率的程序完全是另外一回事。对于 C++ ——一种以拥有非同寻常的能力范围和表现力而自豪的语言——更是尤其如此。...

    Effective C++ 中文版

    有人说C++程序员可以分成两类,读过Effective C++的和没读过的。世界顶级C++大师Scott Meyers成名之作的第三版的确当得起这样的评价。当您读过《Effective C++中文版(第3版改善程序与设计的55个具体做法)》后,就...

    《Effective C++中文第三版》 高清PDF

    《Effective C++》是各读书圈中力鉴的学习C++的必读书籍...C++的功能多,实现复杂,只是学习语法只会纸上谈兵,而《Effective C++》让我去理解C++程序的设计原理、应用方法、陷阱... ...加深了我对计算机,编程的认识。

    More Effective C++

    和其前一本兄弟书籍 Effective C++一样,More Effective C++对每一位以C++为开发工具的程序员而言,都必备读物。  继 Effective C++ 之後,Scott Meyers 於 1996 推出这本「续集」。条款变得比较少,页数倒是多了...

    Effective C++

    Effective C++(编程的50个细节)着重讲解了编写C++程序应该注意的50个细节问题,书中的每一条准则描述了一个编写出更好的C++的方式,每一个条款的背后都有具体范例支持,书中讲的都是C++的编程技巧和注意事项,很多都...

    Effective C++中文第三版高清

    C++经典必读书籍之一,整本书的知识点全面细致,每一个...C++的功能多,实现复杂,只是学习语法只会纸上谈兵,而《Effective C++》去理解C++程序的设计原理、应用方法、陷阱可以.加深了对计算机,编程的理解和认识。

    Effective C++中文版

    Effective C++中文版,包括C、C++内存管理,设计与声明,类和函数的实现,继承和面向对象的设计等

    Effective+C+++3rd+chm+中文版(代码加亮)

    例如,在 Chapter 2(第二章),我希望能告诉你关于 constructors(构造函数),destructors(析构函数),以及 assignment operators(赋值运算符)正确实现的全部内容,但是我假设你已经知道或者能在别处找到这些...

    effective c++

    侯捷翻译的effective c++。是学习c++进阶必读的c++经典。书中提出的50条在c++进行编程时的条例,能够带领你真正的深入了解c++这么语言的实现机制和特点。

    设计模式精解-GoF 23种设计模式解析附C++实现源码

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码 http://www.mscenter.edu.cn/blog/k_eckel 式种的某一个正好可以很好的解决问题,到自己设计的elegant的系统时候的喜悦与思考;一直到最后向别人去讲解设计模式...

    Effective C++(第三版)

    1. 让自己习惯c++ accustoming yourself to c++ 条款01:视c++ 为一个语言联邦 view c++ as a federation of languages 条款02:尽量以const, enum, inline替换 #define prefer consts,enums, and inlines to #...

    Effective C++第3版中文版

    例如,在 Chapter 2(第二章),我希望能告诉你关于 constructors(构造函数),destructors(析构函数),以及 assignment operators(赋值运算符)正确实现的全部内容,但是我假设你已经知道或者能在别处找到这些...

    神书-C++ STL源码解析-高清完整版

    神书-STL实现原理,对于强化数据结构-算法的程序员必备、必读书籍。The Best-Selling Programmer Resource–Now Updated for C++11 The C++ standard library provides a set of common classes and interfaces that...

    高级java笔试题-Lookoop:学习笔记

    c++ c++primer - c++primer顺序容器与关联容器的一些用法 effective c++ - effective c++笔记归纳 Data Structures and Algorithm Analysis 数据结构与一些算法,来自算法导论,数据结构与算法分析-C语言描述,C ...

    Effective.C++.中文第二版.50条款doc文档.chm

    Effective.C++.中文第二版,大小 1 Mb,chm 格式,作者:Scott Meyers,翻译:Lostmouse。 内容预览: 第一章 从C转向C++ 条款1:尽量用const和inline而不用#define 条款2:尽量用而不用 条款3:尽量用new和delete而...

    优化算法 SCEUA算法C++实现 附Python、MATLAB、Fortran代码

    笔者用C++实现了SCE-UA算法,并用常见的测试函数进行了测试! 可以访问本人的两篇博客,内有详细介绍: 【算法】02 SCE-UA简介及源代码 https://blog.csdn.net/weixin_43012724/article/details/121401083 【算法】...

    电脑游戏的创意与实现-牌类的毕业设计说明书

    使用VISUAL C++ ,包括相关的外文文献翻译(Effective C++) 摘 要 III ABSTRACT IV 1前 言 1 1.1中国游戏产业发展现状 1 1.1.1游戏技术的发展现状 1 1.1.2游戏内容架构及设计开发 1 1.1.3游戏市场营销管理及模块...

    Effective+C#+中文版改善C#程序的50种方法

     本书秉承了Scott Meyers的Effective C++和Joshua Bloch的Effective Java所开创的伟大传统.用真实的代码示例,通过清晰、贴近实际和简明的阐述,以条款格式为广大程序员提供凝聚了业界经验结晶的专家建议。  本书...

Global site tag (gtag.js) - Google Analytics