接口(Item 34)。这种类没有数据成员,没有构造函数,有一个虚析构函数(item7)和一系列纯虚函数。
接口类同Java和.NET中的接口是类似的,但Java和.NET对接口强加了限制,c++却没有这做。举个例子,Java和.NET都不允许在接口中声明数据成员或者实现函数,但C++对这两者都没有限制。C++的这种更强的灵活性是有用的。在Item36中解释道,在一个继承体系中应该为所有类实现相同的非虚函数,因此对于在接口类中被声明的函数,作为接口类的一部分对其进行实现是有意义的。
一个Person类的接口实现可能像下面这个样子:
1 class Person {
2
3 public:
4
5 virtual ~Person();
6
7 virtual std::string name() const = 0;
8
9 virtual std::string birthDate() const = 0;
10
11 virtual std::string address() const = 0;
12
13 ...
14
15 };
这个类的用户必须依靠Person指针或者引用来进行编程,因为不可能实例化包含纯虚函数的类。(然而实例化Person的派生类却是可能的)。就像句柄类的用户一样,接口类只有在其接口发生变化的情况下才需要重新编译,其它情况都不需要。
一个接口类的用户必须要有创建新对象的方法。通常情况下,这通过调用扮演派生类构造函数角色的函数来实现,当然派生类是可以被实例化的。这样的函数通常被叫做工厂函数(Item13)或者虚构造函数(virtual constructors)。它们返回指向动态分配对象的指针(用智能指针比较好Item 18)。这样的函数在接口类中通常被声明为static:
1 class Person {
2
3 public:
4
5 ...
6
7 static std::tr1::shared_ptr<Person> // return a tr1::shared_ptr to a new
8
9 create(const std::string& name, // Person initialized with the
10
11 const Date& birthday, // given params; see Item18 for
12
13 const Address& addr); // why a tr1::shared_ptr is returned
14
15 ...
16
17 };
用户像下面这样使用:
1 std::string name;
2
3 Date dateOfBirth;
4
5 Address address;
6
7 ...
8
9 // create an object supporting the Person interface
10
11 std::tr1::shared_ptr<Person> pp(Person::create(name, dateOfBirth, address));
12
13 ...
14
15 std::cout << pp->name() // use the object via the
16
17 << " was born on " // Person interface
18
19 << pp->birthDate()
20
21 << " and now lives at "
22
23 << pp->address();
24
25 ... // the object is automatically
26
27 // deleted when pp goes out of
28
29 // scope — see Item13
当然,必须定义支持接口类接口的具现类,并且在具现类中必须调用真正的构造函数。这在包含了虚构造函数实现的文件中都会发生。举个例子:Person接口类可能有一个具现化派生类RealPerson,它为继承自基类的虚函数提供了实现:
1 class RealPerson: public Person {
2
3 public:
4
5 RealPerson(const std::string& name, const Date& birthday,
6
7 const Address& addr)
8
9 : theName(name), theBirthDate(birthday), theAddress(addr)
10
11 {}
12
13 virtual ~RealPerson() {}
14
15 std::string name() const; // implementations of these
16
17 std::string birthDate() const; // functions are not shown, but
18
19 std::string address() const; // they are easy to imagine
20
21 private:
22
23 std::string theName;
24
25 Date theBirthDate;
26
27 Address theAddress;
28
29 };
给出RealPerson的定义后,实现Person::create就变得微不足道了:
1 std::tr1::shared_ptr<Person> Person::create(const std::string& name,
2
3 const Date& birthday,
4
5 const Address& addr)
6
7 {
8
9 return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday,
10
11 addr));
12
13 }
Person::create的一个更加现实的实现是创建不同的派生类对象,对象类型可能会依赖于额外的函数参数,从文件或者数据库中读取的数据或环境变量等等。
实现一个接口类有两个最普通的机制,RealPerson展示出了其中的一个:它的接口继承自接口类(Person),然后在接口中实现函数。实现接口类的第二种方法涉及到多继承,在Item40中会涉及到这个话题。
7. 使用接口类和句柄类需要花费额外的开销
句柄类和接口类将接口从实现中解耦出来,从而减少了文件间的编译依赖。你可能会问,这种伎俩会让我付出什么?答案也是计算机科学中的常见回答:它会让运行时速度变慢,
另外会为每个对象分配额外的空间。
在句柄类的例子中,成员函数必须通过指向实现的指针才能到达对象数据。这为每次访问添加了一个间接层。你必须将这个实现指针的大小添加到存储每个对象需要的内存容量上。最后,实现指针必须被初始化为指向动态