抽象基类:如何为包含指向(抽象)基类指针的类定义复制构造函数或赋值操作符
Abstract Base Classes: How do you define a copy constructor or assignment operator for a class that contains a pointer to a (abstract) base class?
我刚刚在parashift.com上遇到了一个关于c++抽象基类的问题。
作者提出了在抽象基类中创建纯虚成员函数Clone()
的解决方案。这个函数的目的是创建并返回ABC的克隆对象所指向的地址。这里我有点困惑,如果我们不这样做而实现相同的目的,那么创建这个虚函数并重写赋值操作符和复制构造函数有什么用呢?
class Shape {
public:
// ...
virtual Shape* clone() const = 0; // The Virtual (Copy) Constructor
// ...
};
然后在每个派生类中实现这个clone()
方法。下面是派生类Circle的代码:
class Circle : public Shape {
public:
// ...
virtual Circle* clone() const;
// ...
};
Circle* Circle::clone() const
{
return new Circle(*this);
}
现在假设每个Fred对象"有一个"Shape对象。Fred对象自然不知道Shape是Circle还是Square还是。Fred的复制构造函数和赋值运算符将调用Shape的clone()
方法来复制对象:
class Fred {
public:
// p must be a pointer returned by new; it must not be NULL
Fred(Shape* p)
: p_(p) { assert(p != NULL); }
~Fred()
{ delete p_; }
Fred(const Fred& f)
: p_(f.p_->clone()) { }
Fred& operator= (const Fred& f)
{
if (this != &f) { // Check for self-assignment
Shape* p2 = f.p_->clone(); // Create the new one FIRST...
delete p_; // ...THEN delete the old one
p_ = p2;
}
return *this;
}
// ...
private:
Shape* p_;
};
我认为我们可以在不重写赋值操作符或复制构造函数的情况下实现上述行为。如果我们有两个Fred类型的对象f1
(P_指向Circle)和f2
(P_指向Square)。
f1=f2; // This line exhibits the same behavior what above code is doing.
在默认情况下,f2
的P_
(Address of Square)将被复制到P_
到f1
。现在f1
将指向Square。我们唯一需要注意的是删除Circle的对象,否则它将处于悬空状态。
为什么作者提到上述技术来解决这个问题?请建议。
你确实可以做到
delete f1.p;
f1 = f2;
但是这意味着Fred类的用户——不一定是它的作者——必须知道他必须首先调用delete f1.p
。这对您来说可能是显而易见的,但其他人可能会非常惊讶,一个简单的赋值会导致内存泄漏。此外,如果您在很长一段时间后返回代码,可能您自己忘记了这条小规则并犯了错误。
由于总是必须在赋值Fred之前删除形状,因此在重写的等于操作符中编写此操作绝对是明智的。因此,删除是自动发生的,用户无需担心。
编辑回答评论中的问题:
基类中的virtual Shape *clone()
函数强制每个派生类实现clone()函数。如果您从Shape派生而忘记实现clone()
,那么您的代码将无法编译。这很好,因为Fred的重写赋值操作符依赖于它。
在这种情况下,您想要对Fred
对象进行深度复制。由于析构函数执行delete p_;
,如果有两个Fred
对象指向同一个Shape
,则会得到双重错误。clone()
接口的原因是Fred
不知道p_
指向什么类型的对象,所以它不能直接调用正确的复制构造函数。相反,它依赖于Shape
的子类来创建自己的副本,并使用虚拟方法调度来创建正确类型的对象。
示例并没有试图将f2分配给f1,因此OP认为f1 = f2;
表现出相同的行为是不正确的。该示例将f2的副本赋值给f1,因此行为更接近f1 = new Whatever_f2_Is(*f2)
。
由于f2是指向基类的指针,因此此时没有足够的信息来知道使用哪个复制构造函数(这是一个小谎言,但clone方法仍然更容易使用)。即使Shape不是纯虚拟的,也不能调用new Shape(),因为Shape不知道子类的额外信息。你会有一个形状,但失去了圆形或方形的所有额外方面。
幸运的是,虽然我们不知道f2上的对象是什么,但f2上的对象仍然知道它是什么,我们可以将副本的创建委托给它。这就是克隆方法的作用。
另一种选择是玩is-a游戏或使用ID码和工厂。
- 如何定义一个纯抽象基类
- 将抽象基类中的所有纯虚函数定义为 varaidaic 模板
- 在C++单元测试上下文中,抽象基类是否应将其他抽象基类作为函数参数
- 有没有一种方法可以使用SFINAE来检测一个类型是否实现了给定的抽象基类
- 自定义异常中的用户定义的空构造函数,具有多个继承和抽象基类
- 如何保存指向抽象基类的指针/引用,但在 c++ 中仍然可以复制
- C++:在共享对象中调用抽象基类构造函数/未定义的符号
- 从抽象基类中获取重载函数
- 如何构建一个 Map,其中键是抽象基类(而不是值)
- 无法调用纯抽象基类超载
- 私下继承C++抽象基类是什么意思
- 如何在抽象基类中实现运算符+
- 如何为抽象基类创建模板实例化?
- 崩溃 __cxxabiv1::__cxa_pure_virtual () - vtable ptr 到抽象基类
- CRTP - 是否可以创建一个抽象基类?
- 嵌套抽象基类的泛型模板
- 抽象基类的派生类未正确覆盖基纯虚拟方法
- 在C 中的数组中存储抽象基类的不同派生类的对象
- "Has a" C++关系,最佳做法是让一个类"implement"多个抽象基类?
- C++ 重新定义抽象基类