这一章叙述了实现过程中应该考虑的一些问题,例如,太快定义变量可能造成效率上的拖延;过度使用转型可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象“对象内部数据之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++》和《More Effective C++》是两本由Scott Meyers撰写的经典C++编程指南,深受程序员喜爱。这两本书深入探讨了C++编程的最佳实践和常见陷阱,帮助开发者写出更高效、更安全的代码。以下是对这两本书...
《Effective C++》和《More Effective C++》是两本非常经典的C++编程指南,由Scott Meyers撰写,旨在帮助开发者提升C++编程技巧和理解深度。这两本书中包含了一系列的编程实践和建议,旨在让程序员写出更高效、更...
根据提供的标题、描述和标签,我们可以确定这与《Effective C++》这本书的相关资源有关。但是,在给定的部分内容中,并没有直接提及与本书的具体知识点,而是反复提到了一个名为“Linux公社”的网站及其涵盖的内容...
《Effective C++》和《More Effective C++》是C++编程领域中的两部经典之作,由Scott Meyers撰写。这两本书深入浅出地探讨了如何更有效地利用C++语言特性,提升代码质量和效率,是每一位C++程序员必备的参考书籍。 ...
《Effective C++》是一本由Scott Meyers编著的经典C++编程指南,旨在帮助程序员写出更高效、更安全、更易于理解和维护的C++代码。这本书分为三个版本,分别是第一版、第二版和第三版,其中第一版和第二版提供了中文...
《Effective C++》是C++编程领域的一本经典著作,由Scott Meyers撰写,侯捷翻译为中文版。这本书的第三版对原有的内容进行了更新,以适应C++语言的最新发展,包括C++11、C++14以及C++17等标准的新特性。在阅读这本书...
《Effective C++中文第3版》是一本由Scott Meyers所著的经典著作,该书深入浅出地介绍了C++编程语言的最佳实践与设计原则。它不仅适合初学者掌握C++的核心概念,也是资深程序员提高代码质量和性能的宝典。下面,我们...
《Effective C++》和《More Effective C++》是两本由Scott Meyers撰写的经典C++编程指南,深受程序员喜爱。这些书籍深入探讨了如何利用C++语言的特性来编写更高效、更易于理解和维护的代码。以下是这两本书中涵盖的...
《Effective C++》是C++编程领域中一本极具影响力的经典著作,由Scott Meyers撰写,旨在帮助程序员写出更高效、更可靠、更易于维护的C++代码。这本书深入浅出地探讨了C++编程实践中的一些关键问题,揭示了许多隐藏的...
《More Effective C++》与《Effective C++》是两本非常经典的C++编程指南,由Scott Meyers撰写,对于深入理解和提升C++编程技巧有着极大的帮助。这两本书都是C++程序员的必读之作,旨在帮助读者更好地掌握C++语言的...
根据提供的信息,“Effective C++中文第三版”这本书是C++编程领域的一本经典著作,主要目的是帮助程序员提高C++编程技巧、理解最佳实践并避免常见的陷阱。虽然给出的部分内容并没有包含具体的章节信息或具体内容,...
《Effective C++ 第三版高清英文原版》是一本由Scott Meyers所著的关于C++编程实践的书籍。由于文件中提供的部分摘录存在OCR扫描错误和不完整的问题,将尽可能地从中提取知识点并通顺地表达。 首先,从书中的赞誉和...
《Effective C++中文版(第三版)》是一本深入探讨C++编程实践的书籍,该书由Scott Meyers撰写,是C++编程领域内的经典之作。书中通过条目式讲解,简洁明了地介绍了55个改善C++程序和设计的具体方法。这些方法不仅...
《Effective C++》和《More Effective C++》是两本由Scott Meyers撰写的经典C++编程指南,深受程序员喜爱。这两本书深入探讨了如何利用C++语言的特性来编写高效、可维护的代码,是提升C++编程技能的重要参考资料。 ...
《More Effective C++》是C++编程领域的一本经典书籍,由Scott Meyers撰写,它扩展了《Effective C++》中的思想,深入探讨了C++编程中的更多高级和复杂主题,帮助开发者提升代码质量、效率和可维护性。这本书不仅...
### More Effective C++ 简体中文版(pdf 版).pdf #### 书籍概述 《More Effective C++》是一本由 Scott Meyers 所著的经典著作,旨在帮助程序员更好地掌握 C++ 的高级特性,并有效地应用于实际编程中。本书分为多...
《Effective C++》和《More Effective C++》是两本由Scott Meyers撰写的经典C++编程指南,深受程序员喜爱。这两本书旨在帮助开发者更好地理解和利用C++语言的特性和设计原则,提升代码质量与效率。现在我们来深入...
- **研究成果:** 尽管这项研究未能完全实现预期目标,但市场上已出现多种商业化的C++检测产品,例如静态分析工具。 #### 五、编程规则与实践 - **编程规则局限性:** 许多优秀的C++程序员所遵循的规则难以形式化...