【问题标题】:copying c++ abstract classes复制 C++ 抽象类
【发布时间】:2011-09-06 14:39:51
【问题描述】:

好的,这里有一些代码。

#include <iostream>
#include <deque>
using namespace std;
class A
{
public:
    virtual void Execute()
    {
        cout << "Hello from class A" << endl;
    }
};
class B: public A
{
public:
    void Execute()
    {
        cout << "Hello from class B" << endl;
    }
};
void Main()
{
    deque<A *> aclasses = deque<A*>(0);
    deque<A *> aclasses2 = deque<A*>(0);
    A a1 = A();
    B b1 = B();
    aclasses.push_back(&a1);
    aclasses.push_back(&b1);
    aclasses[0]->Execute();
    aclasses[1]->Execute();

    //Now say I want to copy a class from aclasses to aclasses2
    //while perserving it's identity and making it a seperate entity, without
    //knowing the exact type it is.

    aclasses2.push_back(new A(*aclasses[0]));
    aclasses2.push_back(new A(*aclasses[1]));
    //Now my problem show itself
    for each(A * a in aclasses2)
        a->Execute();
    //Execute is called from the original class A both times.

}

现在你可能会说,为什么不把第一个双端队列的指针放到第二个双端队列中呢?虽然我可以,但我需要数据是独立的。基本上我希望能够从第一个双端队列中克隆项目,同时保留那里的身份并给他们自己的数据。

现在是修改后的版本

#include <iostream>
#include <deque>
using namespace std;
class A
{
public:
    virtual void Execute()
    {
        cout << "Hello from class A" << endl;
    }
    virtual ~A() {}             // don't forget the virtual destructor
    virtual A* clone() const {
       return new A(*this);
    }
};
class B: public A
{
public:
    void Execute()
    {
        cout << "Hello from class B" << endl;
    }
    virtual B* clone() {     // return type is co-variant
       return new B( *this );
    }
};
void MainRUNNER()
{
    deque<A *> aclasses = deque<A*>(0);
    deque<A *> aclasses2 = deque<A*>(0);
    A a1 = A();
    B b1 = B();
    aclasses.push_back(&a1);
    aclasses.push_back(&b1);
    aclasses[0]->Execute();
    aclasses[1]->Execute();

    //Now say I want to copy a class from aclasses to aclasses2
    //while perserving it's identity and making it a seperate entity, without
    //knowing the exact type it is.

    aclasses2.push_back(aclasses[0]->clone());
    aclasses2.push_back(aclasses[1]->clone());
    //Now my problem show itself
    for each(A * a in aclasses2)
        a->Execute();
    //Execute is called from the original class A both times.
}

【问题讨论】:

  • void Main => int main.
  • 这里看不到抽象类 (:
  • 为什么要用指针创建deque-s?这有什么原因吗?如果要复制元素,只需使用deque&lt; A &gt;deque&lt; B &gt;
  • 双端队列包含对象,而不是类。对象是类的一个实例

标签: c++ arrays inheritance


【解决方案1】:

处理的常见模式是通过基类中的虚拟clone() 方法来创建适当类型的新对象:

struct base {
    virtual ~base() {}             // don't forget the virtual destructor
    virtual base* clone() const { 
       return new base(*this); 
    }
};
struct derived : base {
    virtual derived* clone() const {     // return type is co-variant
       return new derived( *this );
    }
};
int main() {
   std::auto_ptr<base> b1( new derived );
   std::auto_ptr<base> b2( b1->clone() ); // will create a derived object
}

【讨论】:

  • 如果可以的话,我会再次对此表示赞成,因为我记得将析构函数设为虚拟。
  • 如果这行得通,你就让我在整个项目附近重写该死的。有趣的是我想出了同样的想法,创建一个虚拟克隆功能,但似乎没有用,我不记得为什么了。我看到的唯一区别是虚拟析构函数。
  • @kelton52:虚拟析构函数仅用于销毁容器中的对象。另请注意,如果对象是动态分配的,那么知道谁负责释放对象很重要,这意味着将指向堆栈分配对象的指针与指向动态分配对象的指针混合在一起通常是一个坏主意...
  • 还有什么我应该知道的吗?我可以内联所有这些函数吗?
  • 没有理由不内联这些函数,但是如果您要多态地调用它们(主要目的是),无论它们是否被声明为内联,编译器都不会内联调用,因为它需要使用动态调度机制,即编译器不知道它需要调用哪些函数。
【解决方案2】:

您需要提供一个虚拟复制构造函数——通常这是一个名为clone的方法——它在每个类中被重写以返回正确的类型:

class A {
    virtual A* clone() {
        return new A();
    }
};

class B : public A {
    void A* clone() {
        return new B();
    }
};

为了复制整个状态,这些方法当然可以任意复杂。

当然,这会泄漏相当多的内存。使用适当的智能指针而不是原始指针(例如,std::shared_ptr 如果您的编译器支持,boost::shared_ptr 否则)。

【讨论】:

  • 你不能从 shared_ptr 中创建向量或双端队列,但我在我的实际代码中实现了linked_ptrs。
【解决方案3】:

你在下面有new A(...)。调用的是A 的复制构造函数(由编译器隐式创建。

你想要的是一个clone 方法。见here。它概括了优秀的C++ Coding Standards 书中的适当项目。下面是最终解决方案的无耻副本,它还显示了很好地使用NVI idiom 来避免slicing 问题。

class A {// …
public:
  A* Clone() const {                        // nonvirtual
    A* p = DoClone();
    assert( typeid(*p) == typeid(*this) && "DoClone incorrectly overridden" );
    return p;                                // check DoClone's returned type
  }

protected:
 A( const A& );
 virtual A* DoClone() const = 0;
};

class B : public A { // …
public:
  virtual B* Clone() const {return new B(*this); }

protected:
  B( const B& rhs ) : A( rhs ) {/* … */}
};

更新 一点解释。克隆的基本思想与这里的其他优秀答案相同。

现在,克隆有切割对象的危险。例如,如果派生自 A 的某个对象忘记实现自己的 clone 方法,则对 A* a = d-&gt;clone() 的调用将不会返回完整的 D 对象(假设 DA 的后代)

NVI 习语说将公共接口与虚拟接口分开。因此,在此示例中,clonepublic,但不是 virtual。它调用protected virtual 方法doClone,它执行实际的克隆,以及派生对象也实现。因为拆分,clone方法可以验证克隆对象的类型是否与原始对象的类型匹配。

【讨论】:

  • 我觉得这是正确的答案,但我不知道这里发生了什么。
  • 您能否解释一下 NVI 如何避免切片?我们使用 NVI 有其他好处,但我认为它对切片没有帮助。
  • 使用 NVI 成语,基类的克隆可以在虚拟克隆操作之前/之后执行操作。在此示例中,它执行(运行时)检查派生类是否记得实现克隆方法。如果与 David 的初始解决方案一起使用,那么断言会发现派生类没有实现正确的克隆方法(即 const)。
  • @kelton52 了解完整背景,请参阅一些链接。我会修改答案来简单解释一下。
  • @Konrad 还应该提到复制 ctor 不是公开的。赋值操作也应该是非公开的。因此,如果我没记错的话,只有在派生对象忘记实现 clone 方法时才会发生切片。 (或者,如果 cctor & operator= 受到保护,派生对象可能会滥用它们)
【解决方案4】:

我认为您将 classesobjects 混淆了,即这些类的实例。

您的容器aclasses 存储指针现有 对象。您可以将同一个指针在许多不同的容器中多次推送,这称为克隆。

【讨论】:

  • 除非这明确是 OP 不想做的。
猜你喜欢
  • 2012-06-28
  • 2016-03-17
  • 2019-04-30
  • 2017-11-27
  • 2011-06-12
  • 2012-11-29
  • 2012-11-27
  • 2011-10-10
  • 2017-07-03
相关资源
最近更新 更多