12

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

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 类型吗?

4

4 回答 4

16

不,不会的。

vector<Instruction> ins;

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

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

vector<Instruction*> ins

或者,更好的是:

vector< std::reference_wrapper<Instruction> > ins

我喜欢这篇博文来解释reference_wrapper

这种行为称为对象切片。

于 2013-04-21T00:13:53.497 回答
8

所以你需要某种指针。Astd::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,则管理起来有点棘手,因为始终需要移动语义,但它避免了一些原子操作的成本。

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

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

于 2013-04-21T00:14:36.630 回答
3

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

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

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

于 2013-04-21T00:13:51.987 回答
0

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

于 2013-04-21T00:35:41.843 回答