我有这个 pimpl 设计,其中实现类是多态的,但接口应该只包含一个指针,使它们多态在某种程度上违背了设计的目的。
所以我创建了我的 Impl 和 Intf 基类来提供引用计数。然后用户可以创建他们的实现。一个例子:
class Impl {
mutable int _ref;
public:
Impl() : _ref(0) {}
virtual ~Impl() {}
int addRef() const { return ++_ref; }
int decRef() const { return --_ref; }
};
template <typename TImpl>
class Intf {
TImpl* impl;
public:
Intf(TImpl* t = 0) : impl(0) {}
Intf(const Intf& other) : impl(other.impl) { if (impl) impl->addRef(); }
Intf& operator=(const Intf& other) {
if (other.impl) other.impl->addRef();
if (impl && impl->decRef() <= 0) delete impl;
impl = other.impl;
}
~Intf() { if (impl && impl->decRef() <= 0) delete impl; }
protected:
TImpl* GetImpl() const { return impl; }
void SetImpl(... //etc
};
class ShapeImpl : public Impl {
public:
virtual void draw() = 0;
};
class Shape : public Intf<ShapeImpl> {
public:
Shape(ShapeImpl* i) : Intf<ShapeImpl>(i) {}
void draw() {
ShapeImpl* i = GetImpl();
if (i) i->draw();
}
};
class TriangleImpl : public ShapeImpl {
public:
void draw();
};
class PolygonImpl : public ShapeImpl {
public:
void draw();
void addSegment(Point a, Point b);
};
这是哪里有问题。Polygon 类有两种可能的声明:
class Polygon1 : public Intf<PolygonImpl> {
public:
void draw() {
PolygonImpl* i = GetImpl();
if (i) i->draw();
}
void addSegment(Point a, Point b) {
PolygonImpl* i = GetImpl();
if (i) i->addSegment(a,b);
}
};
class Polygon2 : public Shape {
void addSegment(Point a, Point b) {
ShapeImpl* i = GetImpl();
if (i) dynamic_cast<Polygon*>(i)->addSegment(a,b);
}
}
在Polygon1中,我重写了draw的代码,因为我没有继承它。在 Polygon2 中,我需要丑陋的动态转换,因为 GetImpl() 不知道 PolygonImpl。我想做的是这样的:
template <typename TImpl>
struct Shape_Interface {
void draw() {
TImpl* i = GetImpl();
if (i) i->draw();
}
};
template <typename TImpl>
struct Polygon_Interface : public Shape_Interface<Timpl> {
void addSegment(Point a, Point b) { ... }
};
class Shape : public TIntf<ShapeImpl>, public Shape_Interface<ShapeImpl> {...};
class Polygon : public TIntf<PolygonImpl>, public Polygon_Interface<PolygonImpl> {
public:
Polygon(PolygonImpl* i) : TIntf<PolygonImpl>(i) {}
};
但是这里当然有问题。除非我从 Intf 派生它们,否则我无法从接口类访问 GetImpl()。如果我这样做,我需要让 Intf 出现在它出现的任何地方都是虚拟的。
template <typename TImpl>
class PolygonInterface : public virtual Intf<TImpl> { ... };
class Polygon : public virtual Intf<PolygonImpl>, public PolygonInterface { ... }
或者我可以在每个接口中存储一个 TImpl*& 并使用对基本 Intf::impl 的引用来构造它们。但这只是意味着对于包含的每个接口,我都有一个指向我自己的指针。
template <typename TImpl>
class PolygonInterface {
TImpl*& impl;
public:
PolygonInterface(TImpl*& i) : impl(i) {}
...};
这两种解决方案都使 Intf 类膨胀,添加了额外的取消引用,并且基本上没有比直接多态性提供任何好处。
所以,问题是,除了在各处复制代码(存在维护问题)之外,我是否错过了第三种方法来解决这个问题?
完全应该,但不起作用:我希望基类联合只是覆盖类布局,并且对于多态类,要求它们具有完全相同的 vtable 布局。然后 Intf 和 ShapeInterface 都将各自声明一个 T* 元素并以相同的方式访问它:
class Shape : public union Intf<ShapeImpl>, public union ShapeInterface<ShapeImpl> {};