学习effective c++笔记(33-34)

来源:互联网 发布:淘宝图片保护惩罚 编辑:程序博客网 时间:2024/06/17 01:08

33.
要牢记在心的一条是,inline指令就象register,它只是对编译器的一种提示,而不是命令。也就是说,只要编译器愿意,它就可以随意地忽略掉你的指令,事实上编译器常常会这么做。
可以归结为:一个给定的内联函数是否真的被内联取决于所用的编译器的具体实现。幸运的是,大多数编译器都可以设置诊断级,当声明为内联的函数实际上没有被内联时,编译器就会为你发出警告信息.

无论新规则还是旧规则,如果内联函数没被内联,每个调用内联函数的地方还是得承担函数调用的开销;如果是旧规则,还得忍受代码体积的增加,因为每个包含(或调用) f的被编译单元都有一份f的代码及其静态变量的拷贝!(更糟糕的是,每个f的拷贝以及每个f的静态变量的拷贝往往处于不同的虚拟内存页面,所以两个对f的不同拷贝进行调用有可能导致多个页面错误。)

inline void f() {...}            // 同上
void (*pf)() = f;                // pf指向f
int main()
{
  f();                           // 对f的内联调用
  pf();                          // 通过pf对f的非内联调用
  ...
}
这种情况似乎很荒谬:f的调用被内联了,但在旧的规则下,每个取f地址的被编译单元还是各自生成了此函数的静态拷贝。(新规则下,不管涉及的被编译单元有多少,将只生成唯一一个f的外部拷贝)

即使你从来不使用函数指针,这类"没被内联的内联函数"也会找上你的门,因为不只是程序员会使用函数指针,有时编译器也这么做。特别是,编译器有时会生成构造函数和析构函数的外部拷贝,这样就可以通过得到那些函数的指针,方便地构造和析构类的对象数组.

想对程序库中的内联函数进行二进制代码升级是不可能的。换句话说,如果f是库中的一个内联函数,用户会将f的函数体编译到自己的程序中。如果程序库的设计者后来要修改f,所有使用f的用户程序必须重新编译。这会很令人讨厌(参见条款34)。相反,如果f是非内联函数,对f的修改仅需要用户重新链接,这就比需要重新编译大大减轻了负担;如果包含这个函数的程序库是被动态链接的,程序库的修改对用户来说完全是透明的。

如果函数中包含静态对象,通常要避免将它声明为内联函数。

一般来说,实际编程时最初的原则是不要内联任何函数,除非函数确实很小很简单.

80-20定律:一个程序往往花80%的时间来执行程序中20%的代码。

一旦找出了程序中那些重要的函数,以及那些内联后可以确实提高程序性能的函数(这些函数本身依赖于所在系统的体系结构),就要毫不犹豫地声明为inline。同时,要注意代码膨胀带来的问题,并监视编译器的警告信息,看看是否有内联函数没有被编译器内联。

34.
在将接口从实现分离这方面,C++做得不是很出色。尤其是,C++的类定义中不仅包含接口规范,还有不少实现细节。

将一个对象的实现隐藏在指针身后离。

下面就是这一思想直接深化后的含义:

· 如果可以使用对象的引用和指针,就要避免使用对象本身。定义某个类型的引用和指针只会涉及到这个类型的声明。定义此类型的对象则需要类型定义的参与。

· 尽可能使用类的声明,而不使用类的定义。因为在声明一个函数时,如果用到某个类,是绝对不需要这个类的定义的,即使函数是通过传值来传递和返回这个类:


这里很重要:
· 不要在头文件中再(通过#include指令)包含其它头文件,除非缺少了它们就不能编译。相反,要一个一个地声明所需要的类,让使用这个头文件的用户自己(通过#include指令)去包含其它的头文件,以使用户代码最终得以通过编译。一些用户会抱怨这样做对他们来说很不方便,但实际上你为他们避免了许多你曾饱受的痛苦。事实上,这种技术很受推崇,并被运用到C++标准库(参见条款49)中;头文件<iosfwd>就包含了iostream库中的类型声明(而且仅仅是类型声明)。


Person类仅仅用一个指针来指向某个不确定的实现,这样的类常常被称为句炳类(Handle class)或信封类(Envelope class)。(对于它们所指向的类来说,前一种情况下对应的叫法是主体类(Body class);后一种情况下则叫信件类(Letter class)。)偶尔也有人把这种类叫 "Cheshire猫" 类,这得提到《艾丽丝漫游仙境》中那只猫,当它愿意时,它会使身体其它部分消失,仅仅留下微笑。
(thinking in c++ 中就有这个猫类的介绍)
------------句柄类-------------------------
#include "Person.h"          // 因为是在实现Person类,
                             // 所以必须包含类的定义

#include "PersonImpl.h"      // 也必须包含PersonImpl类的定义,
                             // 否则不能调用它的成员函数。
                             // 注意PersonImpl和Person含有一样的
                             // 成员函数,它们的接口完全相同

Person::Person(const string& name, const Date& birthday,
               const Address& addr, const Country& country)
{
  impl = new PersonImpl(name, birthday, addr, country);
}

string Person::name() const
{
  return impl- >name();
}

-----或者----协议类---------------------
class Person {
public:
  virtual ~Person();

  virtual string name() const = 0;
  virtual string birthDate() const = 0;
  virtual string address() const = 0;
  virtual string nationality() const = 0;
// makePerson现在是类的成员
  static Person * makePerson(const string& name,
                             const Date & birthday,
                             const Address & addr,
                             const Country & country);
};

class RealPerson: public Person {
public:
  RealPerson(const string & name, const Date& birthday,
             const Address & addr, const Country& country)
  :  name_(name), birthday_(birthday),
     address_(addr), country_(country)
  {}

  virtual ~RealPerson() {}

  string name() const;          // 函数的具体实现没有
  string birthDate() const;     // 在这里给出,但它们
  string address() const;       // 都很容易实现
  string nationality() const;   

private:
  string name_;
  Date birthday_;
  Address address_;
  Country country_;
};
有了RealPerson,写Person::makePerson就是小菜一碟:

Person * Person::makePerson(const string& name,
                            const Date& birthday,
                            const Address & addr,
                            const Country & country)
{
  return new RealPerson(name, birthday, addr, country);
}

句柄类和协议类都不大会使用内联函数。使用任何内联函数时都要访问实现细节,而设计句柄类和协议类的初衷正是为了避免这种情况。

 

 

 

 

 

 

 

 

 

 

原创粉丝点击