A: 没人强迫你这么做。如果你不希望界面中有数据,那么就不要把它放在定义界面的类中,放到继承类中好了。参看“为何我编译一个程序要花那么多时间”条目。[译注:本FAQ中凡原文为declare/declaration的均译为声明;define/definition均译为定义。两者涵义之基本差别参见后面“‘int* p;’和‘int *p;’到底哪个正确”条目中的译注。通常而言,我们还是将下面的示例代码称为complex类的定义,而将单单一行“class complex;”称作声明。]
但也有的时候你确实需要把数据放到类声明里面,比如下面的复数类的例子:
template<class Scalar> class complex {
public:
complex() : re(0), im(0) { }
complex(Scalar r) : re(r), im(0) { }
complex(Scalar r, Scalar i) : re(r), im(i) { }
// ...
complex& operator+=(const complex& a)
{ re+=a.re; im+=a.im; return *this; }
// ...
private:
Scalar re, im;
};
这个complex(复数)类是被设计成像C++内置类型那样使用的,所以数据表示必须出现在声明之中,以便可以建立真正的本地对象(即在堆栈上分配的对象,而非在堆中分配),这同时也确保了简单操作能被正确内联化。“本地对象”和“内联”这两点很重要,因为这样才可以使我们的复数类达到和内置复数类型的语言相当的效率。
[译注:我觉得Bjarne的这段回答有点“逃避问题”之嫌。我想,提问者的真实意图或许是想知道如何用C++将“界面”与“实作”完全分离。不幸的是,C++语言和类机制本身不提供这种方式。我们都知道,类的“界面”部分往往被定义为公有(一般是一些虚函数);“实作”部分则往往定义为保护或私有(包括函数和数据);但无论是“public”段还是“protected”、“private”段都必须出现在类的声明中,随类声明所在的头文件一起提供。想来这就是“为何数据必须放到类声明中”问题的由来吧。为了解决这个问题,我们有个变通的办法:使用Proxy模式(参见《Design Patterns : Elements of Reusable Object-Oriented Software》一书),我们可以将实作部分在proxy类中声明(称为“对象组合”),而不将proxy类的声明暴露给用户。例如:
class Implementer; // forward declaration
class Interface {
public:
// interface private:
Implementer impl;
};
在这个例子中,Implementer类就是proxy。在Interface中暴露给用户的只是一个impl对象的“存根”,而无实作内容。Implementer类可以如下声明:
class Implementer {
public:
// implementation details, including data members
};
上述代码中的注释处可以存放提问者所说的“数据”,而Implementer的声明代码不需暴露给用户。不过,Proxy模式也不是十全十美的——Interface通过impl指针间接调用实作代码带来了额外的开销。或许读者会说,C++不是有内联机制吗?这个开销能通过内联定义而弥补吧。但别忘了,此处运用Proxy模式的目的就是把“实作”部分隐藏起来,这“隐藏”往往就意味着“实作代码”以链接库中的二进制代码形式存在。目前的C++编译器和链接器能做到既“代码内联”又“二进制隐藏”吗?或许可以。那么Proxy模式又能否和C++的模板机制“合作愉快”呢?(换句话说,如果前面代码中Interface和Implementer的声明均不是class,而是template,又如何呢?)关键在于,编译器对内联和模板的支持之实作是否需要进行源码拷贝,还是可以进行二进制码拷贝。目前而言,C#的泛型支持之实作是在Intermediate Language层面上的,而C++则是源码层面上的。Bjarne给出的复数类声明代码称“数据必须出现在类声明中”也是部分出于这种考虑。呵呵,扯远了……毕竟,这段文字只是FAQ的“译注”而已,此处不作更多探讨,有兴趣的读者可以自己去寻找答案 :O) ]
本文乃网上搜集得来,其版权归原作者和原出处所有。如有侵犯版权之处请与我联系,我将马上进行处理。