皮姆普成语与继承
我想用pimpl成语来继承。
这是基础公共类及其实现类:
class A
{
public:
A(){pAImpl = new AImpl;};
void foo(){pAImpl->foo();};
private:
AImpl* pAImpl;
};
class AImpl
{
public:
void foo(){/*do something*/};
};
我希望能够用它的实现类创建派生的公共类:
class B : public A
{
public:
void bar(){pAImpl->bar();}; // Can't do! pAimpl is A's private.
};
class BImpl : public AImpl
{
public:
void bar(){/*do something else*/};
};
但是我不能在B中使用pAimpl,因为它是A的私有。
所以我看到了一些解决方法:
我应该选择什么?
class A
{
public:
A(bool DoNew = true){
if(DoNew)
pAImpl = new AImpl;
};
void foo(){pAImpl->foo();};
protected:
void SetpAImpl(AImpl* pImpl) {pAImpl = pImpl;};
private:
AImpl* pAImpl;
};
class AImpl
{
public:
void foo(){/*do something*/};
};
class B : public A
{
public:
B() : A(false){
pBImpl = new BImpl;
SetpAImpl(pBImpl);
};
void bar(){pBImpl->bar();};
private:
BImpl* pBImpl;
};
class BImpl : public AImpl
{
public:
void bar(){/*do something else*/};
};
我认为从纯粹的面向对象理论的角度来看,最好的方式是不让BImpl继承AImpl(这是你在选项3中的意思吗?)。 然而,如果pmppl成员变量是const
,那么让BImpl从AImpl派生(并将所需的impl传递给A的构造函数)也可以。 使用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替代性原则的微妙违反。 解决方案是:
施工后不要更换小扁豆成员。 声明它们是AImpl * const pimpl
。 然后,派生的构造函数可以传递适当的类型,派生类的其余部分可以自信地下载。 然而,那么你不能做非抛出交换,赋值或写时复制,因为这些技术要求你可以更改pimpl成员。 然而,如果你有一个继承层次结构,你可能并不打算做这些事情。
A和B的私有变量分别与AImpl和BImpl类无关(和哑)。 如果B想对A做些什么,那么使用A的公共或受保护的接口。 这也保留了使用pimpl最常见的原因:能够将AImpl的定义隐藏在派生类无法使用的cpp文件中,所以当A的实现更改时,一半的程序不需要重新编译。
正如stefan.ciobaca所说,如果你真的希望A是可扩展的,你会希望pAImpl
得到保护。
但是,你在B
中定义的void bar(){pAImpl->bar();};
似乎很奇怪,因为bar
是BImpl
上的一种方法,而不是AImpl
。
至少有三个简单的选择可以避免这个问题:
BImpl
扩展AImpl
(继承foo
的现有实现而不是定义另一个)的变体, BImpl
定义bar
, B
使用它的私有BImpl* pBImpl
来访问两者。 B
持有指向AImpl
和BImpl
私人指针,并将每个foo
和bar
转发给适当的实施者。 上一篇: Pimpl idiom with inheritance
下一篇: Using properties instead of fields in class methods of the same unit is a bad practice?