抽象基类和副本构造,经验法则


10

通常,最好有一个抽象基类来隔离对象的接口。

问题在于,默认情况下,C ++中的副本构造IMHO在很大程度上已被破坏,默认情况下会生成副本构造函数。

那么,当您有一个抽象基类和派生类中的原始指针时,会遇到什么陷阱?

class IAbstract
{
    ~IAbstract() = 0;
}

class Derived : public IAbstract
{
    char *theProblem;
    ...
}

IAbstract *a1 = new Derived();
IAbstract a2 = *a1;//???

现在,您是否彻底禁用了整个层次结构的副本构建?将副本构造声明为私有IAbstract

抽象基类是否有三分法则?


1
使用引用代替指针:)
tp1 2012年

@ tp1:或者至少是一些智能指针。
本杰明·班尼尔

1
有时,您只需要使用现有代码即可……您无法立即更改所有内容。
Coder 2012年

您为什么认为默认的复制构造函数已损坏?
2012年

2
@Coder:Google样式指南是一堆垃圾,对于任何C ++开发来说绝对是糟透了。
DeadMG 2012年

Answers:


6

在大多数情况下,抽象类的复制构造以及赋值运算符应私有化。

根据定义,抽象类为多态类型。因此,您不知道实例正在使用多少内存,因此无法安全地复制或分配它。实际上,您有切片风险:https : //stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c

在C ++中,多态类型一定不能通过值来操纵。您可以通过引用或指针(或任何智能指针)来操纵它们。

这就是为什么Java只能通过引用来操作对象,以及C#和D为何将类和结构分开的原因(第一个是多态和引用类型,第二个是非多态和值类型)。


2
Java方面的情况没有任何改善。在Java中,复制任何内容都非常麻烦,即使您确实需要复制,也很容易忘记这样做。因此,您最终会遇到讨厌的错误,其中两个数据结构都引用了相同的值类(例如Date),然后其中一个更改Date,而另一个数据结构现在已损坏。
凯文·克莱恩

3
嗯 这不是问题,任何人都知道C ++不会犯此错误。更重要的是,如果您知道自己在做什么,就根本没有理由按值来操作多态类型。您在技术上可能性很小的情况下开始全盘下意识的反应。NULL指针是一个更大的问题。
DeadMG

8

当然,您可以将其设置为保护并为空,以便派生类可以选择。但是,更普遍地,无论如何都禁止使用代码因为它无法实例化IAbstract-因为它具有纯虚函数。因此,这通常是没有问题的-您的接口类无法实例化,因此不能被复制,您的更多派生类可以根据需要禁止或保持复制。


如果存在一个抽象-> Derived1-> Derived2层次结构,并且将Derived2分配给Derived1怎么办?语言的这些黑暗角落仍然让我感到惊讶。
编码员2012年

@Coder:通常将其视为PEBKAC。然而,更直接,你总是可以声明一个私人operator=(const Derived2&)Derived1
DeadMG

好吧,也许这真的不是问题。我只想确保该班级不受虐待。
编码员2012年

2
为此,您应该获得一枚奖牌。
世界工程师

2
@Coder:虐待谁?您能做的最好的事情就是轻松编写正确的代码。试图防御不了解C ++的程序员是毫无意义的。
凯文·克莱恩

2

通过将ctor和赋值设为私有(或在C ++ 11中将它们声明为= delete),可以禁用复制。

这里的重点是您必须执行此操作。要保留您的代码,IAbstract不是问题。(请注意,执行此操作后,您将*a1 IAbstract子对象分配给a2,失去了对的任何引用Derived。值分配不是多态的)

问题随之而来Derived::theproblem。实际上,将一个派生对象复制到另一个对象中可能会共享*theproblem可能无法共享的数据(可能有两个实例delete theproblem在其析构函数中调用)。

如果是这种情况,那就Derived必须是不可复制且不可分配的。当然,如果你把私人的复制IAbstract,因为默认副本Derived需要它,Derived也将是不可拷贝。但是,如果您定义自己的文件Derived::Derived(const Derived&)而不调用IAbtractcopy,您仍然可以复制它们。

问题出在“派生”中,解决方案必须留在“派生”中:如果它必须是仅由指针或引用访问的仅动态对象,则派生本身必须具有

class Derived
{
    ...
    Derived(const Derived&) = delete;
    Derived& operator=(const Derived&) = delete;
};

从本质上讲,派生类的设计者(应该知道派生的工作方式和theproblem管理方式)由设计师决定如何分配和复制。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.