【问题标题】:Vectors and polymorphism in C++C++ 中的向量和多态性
【发布时间】:2021-06-14 13:37:43
【问题描述】:

我遇到了一个棘手的情况。它的简化形式是这样的

class Instruction
{
public:
    virtual void execute() {  }
};

class Add: public Instruction
{
private:
    int a;
    int b;
    int c;
public:
    Add(int x, int y, int z) {a=x;b=y;c=z;}
    void execute() { a = b + c;  }
};

然后在一节课上我会做类似...

void some_method()
{
    vector<Instruction> v;
    Instruction* i = new Add(1,2,3)
    v.push_back(*i);
}

在另一个班级......

void some_other_method()
{
    Instruction ins = v.back();
    ins.execute();
}

他们以某种方式共享这个指令向量。我关心的是我执行“执行”功能的部分。它会起作用吗?它会保留它的 Add 类型吗?

【问题讨论】:

  • 嗯,你试过了吗?
  • 了解object slicing
  • 让我编辑一些东西
  • 您的some_method() 每次调用都会泄漏内存。
  • 将向量类型更改为指针容器,并移除解引用 (*i)。它会起作用的。

标签: c++ oop vector polymorphism


【解决方案1】:

不,不会的。

vector<Instruction> ins;

存储值,而不是引用。这意味着无论您如何将指令对象放在其中,它都会在将来的某个时候被复制。

此外,由于您使用new 进行分配,因此上述代码会泄漏该对象。如果你想正确地做到这一点,你必须这样做

vector<Instruction*> ins

或者,更好:

vector< std::reference_wrapper<Instruction> > ins

我喜欢这个this blog post来解释reference_wrapper

这种行为称为object slicing.

【讨论】:

  • vector&lt;std::reference_wrapper&lt;Instruction&gt;&gt;要非常小心!您必须绝对确定您添加到向量中的任何内容的生命周期都比向量长,并且您添加的内容确实会在某个时候被删除。改用vector&lt;std::unique_ptr&lt;Instruction&gt;&gt; 会更好。
【解决方案2】:

所以你需要某种指针。 std::shared_ptr 效果很好:

typedef shared_ptr<Instruction> PInstruction;

vector<PInstruction> v;
v.emplace_back(make_shared<Add>());

PInstruction i = v[0];

请记住,PInstruction 是引用计数的,因此 PInstruction 的复制构造函数将为同一对象创建一个新的“引用”。

如果你想复制引用的对象,你必须实现一个克隆方法:

struct Instruction
{

   virtual PInstruction clone() = 0;
   ...
}

struct Add
{
    PInstruction clone() { return make_shared<Add>(*this); }
    ...
}

PInstruction x = ...;
PInstruction y = x->clone();

如果性能是一个问题,而不是您可以查看 std::unique_ptr,则管理起来有点棘手,因为始终需要移动语义,但它避免了一些原子操作的成本。

您还可以使用原始指针并通过某种内存池架构手动管理内存。

根本的问题是,要拥有多态类型,编译器不知道子类会有多大,所以你不能只拥有基本类型的向量,因为它不会有额外的子类所需的空间。出于这个原因,您将需要使用如上所述的传递引用语义。这会将指向对象的指针存储在向量中,然后根据子类的需要将对象存储在不同大小的块中。

【讨论】:

  • @user44273:然后在 Boost 或 TR1 中使用 shared_ptr 实现。
  • @user44273 你明白为什么按值推送对象不起作用吗?这是这个答案最重要的一点。
  • 不完全。你能启发我吗?
  • @user44273 阅读关于对象切片的linked Q&A。它准确地说明了你处理这个问题的方式有什么问题,以及这个答案(和其他几个)用于解决它的信封机制。它会比单独的评论更好地解释它。
  • 嗯...非常感谢!我很高兴我没有继续实施这个并在几个小时后将我的头撞到墙上。
【解决方案3】:

不,那行不通;您正在“切片”Add 对象,并且仅将其 Instruction 部分插入到数组中。我建议您将基类抽象化(例如,通过使 execute 纯虚拟化),以便切片产生编译错误而不是意外行为。

要获得多态行为,向量需要包含指向基类的指针。

然后,您需要小心管理对象本身的方式,因为它们不再包含在向量中。智能指针可能对此有用;并且由于您可能会动态分配这些对象,因此您还应该为基类提供一个虚拟析构函数,以确保您可以正确删除它们。

【讨论】:

    【解决方案4】:

    您可能想要做几件事,A:将“v”的类型更改为“vector”,B:使用“delete”运算符管理您的内存。要回答你的问题,用这种方法,是的,但你只能从“指令”访问接口,如果你知道“指令”指针指向的东西的类型,如果你需要,我建议使用 dynamic_cast从“添加”访问界面。

    【讨论】:

    • 我为内存泄漏道歉。这是一个匆忙编写的代码。不幸的是,我不知道类型。除了这里给出的 Add 之外,还有 Sub、Mul、Div 等等。
    • @user44273 无需道歉,我只是对 dynamic_cast 进行假设。
    猜你喜欢
    • 1970-01-01
    • 2013-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多