我认为从纯粹的面向对象理论的角度来看,最好的方法是不要让 BImpl 从 AImpl 继承(这就是你在选项 3 中的意思吗?)。但是,让 BImpl 从 AImpl 派生(并将所需的 impl 传递给 A 的构造函数)也是可以的,只要 pimpl 成员变量是const
. 使用 get 函数还是直接从派生类访问变量并不重要,除非您想在派生类上强制执行 const 正确性。让派生类改变 pimpl 不是一个好主意——它们可能会破坏 A 的所有初始化——而且让基类改变它也不是一个好主意。考虑对您的示例进行此扩展:
class A
{
protected:
struct AImpl {void foo(); /*...*/};
A(AImpl * impl): pimpl(impl) {}
AImpl * GetImpl() { return pimpl; }
const AImpl * GetImpl() const { return pimpl; }
private:
AImpl * pimpl;
public:
void foo() {pImpl->foo();}
friend void swap(A&, A&);
};
void swap(A & a1, A & a2)
{
using std::swap;
swap(a1.pimpl, a2.pimpl);
}
class B: public A
{
protected:
struct BImpl: public AImpl {void bar();};
public:
void bar(){static_cast<BImpl *>(GetImpl())->bar();}
B(): A(new BImpl()) {}
};
class C: public A
{
protected:
struct CImpl: public AImpl {void baz();};
public:
void baz(){static_cast<CImpl *>(GetImpl())->baz();}
C(): A(new CImpl()) {}
};
int main()
{
B b;
C c;
swap(b, c); //calls swap(A&, A&)
//This is now a bad situation - B.pimpl is a CImpl *, and C.pimpl is a BImpl *!
//Consider:
b.bar();
//If BImpl and CImpl weren't derived from AImpl, then this wouldn't happen.
//You could have b's BImpl being out of sync with its AImpl, though.
}
尽管您可能没有 swap() 函数,但您可以很容易地想到会发生类似的问题,特别是如果 A 是可分配的,无论是偶然还是有意。这有点微妙地违反了 Liskov 可替代性原则。解决方案是:
请勿在施工后更改 pimpl 成员。将它们声明为AImpl * const pimpl
. 然后,派生构造函数可以传递适当的类型,派生类的其余部分可以自信地向下转换。但是,您不能进行非抛出交换、分配或写时复制等操作,因为这些技术要求您可以更改 pimpl 成员。但是,如果您有继承层次结构,您可能并不真的打算做这些事情。
分别为 A 和 B 的私有变量提供不相关(和愚蠢)的 AImpl 和 BImpl 类。如果 B 想对 A 做某事,则使用 A 的公共或受保护接口。这也保留了使用 pimpl 的最常见原因:能够将 AImpl 的定义隐藏在派生类无法使用的 cpp 文件中,因此当 A 的实现发生更改时,一半的程序不需要重新编译。