注意:以下是C ++ 03代码,但是我们希望在未来两年内升级到C ++ 11,因此我们必须牢记这一点。
我正在编写有关如何用C ++编写抽象接口的指南(针对新手)。我已经阅读了有关Sutter的两篇文章,在互联网上搜索了示例和答案,并进行了一些测试。
此代码不得编译!
void foo(SomeInterface & a, SomeInterface & b)
{
SomeInterface c ; // must not be default-constructible
SomeInterface d(a); // must not be copy-constructible
a = b ; // must not be assignable
}
上面的所有行为都在切片时发现了问题的根源:抽象接口(或层次结构中的非叶子类)不应构造,不可复制/可分配,即使派生类可以。
0th解决方案:基本界面
class VirtuallyDestructible
{
public :
virtual ~VirtuallyDestructible() {}
} ;
这个解决方案是简单的,并且有些天真:它克服了我们的所有限制:它可以是默认构造的,复制构造的和复制分配的(我甚至不确定移动构造函数和赋值,但是我还有2年的时间来弄清楚出来)。
- 我们不能将析构函数声明为纯虚函数,因为我们需要保持其内联,并且某些编译器不会使用内联空主体来消化纯虚函数。
- 是的,此类的唯一要点是使实现者实际上可破坏,这是极少数情况。
- 即使我们有一个额外的虚拟纯方法(大多数情况下),该类仍然是可复制分配的。
所以不行...
第一个解决方案:boost :: noncopyable
class VirtuallyDestructible : boost::noncopyable
{
public :
virtual ~VirtuallyDestructible() {}
} ;
该解决方案是最好的,因为它是纯净,清晰和C ++的(无宏)
问题在于它仍然不适用于该特定接口,因为VirtuallyConstructible仍可以默认构造。
- 我们不能将析构函数声明为纯虚函数,因为我们需要保持其内联,并且某些编译器不会消化它。
- 是的,此类的唯一要点是使实现者实际上可破坏,这是极少数情况。
另一个问题是,实现不可复制接口的类如果必须具有这些方法,则必须显式声明/定义复制构造函数和赋值运算符(在我们的代码中,我们仍然可以通过客户端访问这些值类)接口)。
这违反了零规则,这就是我们要去的地方:如果默认实现可以,那么我们应该可以使用它。
第二个解决方案:保护它们!
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
// With C++11, these methods would be "= default"
MyInterface() {}
MyInterface(const MyInterface & ) {}
MyInterface & operator = (const MyInterface & ) { return *this ; }
} ;
这种模式遵循我们的技术限制(至少在用户代码中):MyInterface不能默认构造,不能复制构造,也不能复制分配。
而且,它对实现类没有任何人为的约束,然后可以自由遵循零规则,甚至可以在C ++ 11/14中将一些构造函数/运算符声明为“ = default”而没有问题。
现在,这非常冗长,一种替代方法是使用宏,例如:
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
DECLARE_AS_NON_SLICEABLE(MyInterface) ;
} ;
受保护者必须保留在宏之外(因为它没有作用域)。
正确地“命名空间”(即,以公司或产品的名称为前缀),宏应该是无害的。
这样做的好处是,代码只在一个源中分解,而不是复制粘贴到所有接口中。如果将来以相同的方式显式禁用move-constructor和move-assignment,那么代码中的更改将非常小。
结论
- 我是否希望对代码进行保护以防止在接口中切片?(我相信我不是,但是一个人永远都不知道...)
- 在上述解决方案中最好的解决方案是什么?
- 还有其他更好的解决方案吗?
请记住,这是一种模式,将作为新手(除其他外)的指南,因此,诸如:“每种情况都应实现”的解决方案不是可行的解决方案。
赏金和结果
我将赏金授予coredump,是因为花在回答问题上的时间以及答案的相关性。
我对这个问题的解决方法可能是这样的:
class MyInterface
{
DECLARE_CLASS_AS_INTERFACE(MyInterface) ;
public :
// the virtual methods
} ;
...具有以下宏:
#define DECLARE_CLASS_AS_INTERFACE(ClassName) \
public : \
virtual ~ClassName() {} \
protected : \
ClassName() {} \
ClassName(const ClassName & ) {} \
ClassName & operator = (const ClassName & ) { return *this ; } \
private :
对于我的问题,这是一个可行的解决方案,其原因如下:
- 此类无法实例化(构造函数受到保护)
- 该类几乎可以被摧毁
- 可以在不对继承类施加过多约束的情况下继承此类(例如,继承类默认情况下可以复制)
- 宏的使用意味着接口“声明”易于识别(和搜索),并且其代码集中在一个位置,使其易于修改(适当的前缀名称将消除不希望的名称冲突)
请注意,其他答案也提供了宝贵的见解。谢谢所有尝试的人。
请注意,我想我仍然可以在这个问题上另外悬赏,并且我重视启发性的答案,以至于我应该看到一个答案,我将打开一个赏金以将其分配给该答案。
virtual ~VirtuallyDestructible() = 0
接口类的虚拟继承(仅限抽象成员)。您可能会忽略VirtuallyDestructible。
virtual void bar() = 0;
例如?这样可以防止您的界面被实例化。