【问题标题】:std::move with a unique pointer of a base class pointing to an instance of a derived classstd::move 带有指向派生类实例的基类的唯一指针
【发布时间】:2018-04-02 18:32:28
【问题描述】:

我正在尝试在 C++ 中实现命令和策略设计模式。

我有一个命令界面和一个策略界面(为每个玩家分配不同的策略,因此玩家可能是手动的、AI 攻击性的、AI 防御性的、AI 随机的……等等)。这些策略有一系列函数,它们返回一个 ICommand 类型的唯一指针(例如命令接口),该指针指向派生命令类的实例。这个想法是以特定方式在我的策略中创建一些命令,然后将指向特定命令的唯一指针返回给执行命令的游戏(命令的调用者),而不管该命令是什么。

我的以下问题是:

  • 如果我移动由基类的唯一指针指向的派生类的实例,我会得到对象切片吗?

  • 如果是这样,无论对象的类型如何,处理移动对象的更好方法是什么?

这是我的代码。请注意,我只包含了命令 choose_combo 的代码,因为其余部分有些多余:

我的标准命令有一个接口:

class Igame_command {
protected: // protected, since Igame_command is an interface.
    player &p;
    game_model &gm;

    Igame_command(player p, game_model gm) : p(p), gm(gm) {}

public:
    virtual void execute() = 0;
};

然后我有一些实现上述接口的派生类:

class choose_combo_c : private Igame_command {
    combo c;
    int vp;
public:
    void set_combo(combo c) { this->c = c; }
    void set_vp(int vp) { this->vp = vp; }
    combo get_combo() { return c; }
    int get_vp() { return vp; }
    choose_combo_c::choose_combo_c(player &p, game_model &gm);

    void execute() override;
};

然后我有一个用于我的策略的界面:

class Istrategy {
protected:
application &app;
game_model &gm;
player &p;

Istrategy(application &app, game_model &gm, player &p) : app(app), gm(gm), p(p) {}

public:

virtual std::unique_ptr<Igame_command> choose_combo(
        std::vector<combo> &available_combos, std::vector<int> &available_combos_vp) = 0;

};

例如,对于手动策略,选择组合:

std::unique_ptr<Igame_command> manual_strategy::choose_combo(
        std::vector<combo> &available_combos, std::vector<int> &available_combos_vp) override {

    // code to have the player make a choice, it sets the int choice...    

    choose_combo_c* ccc = new choose_combo_c(p, gm);
    ccc->set_combo(available_combos.at(choice));
    ccc->set_vp(available_combos_vp.at(choice));
    return std::unique_ptr<Igame_command> (new choose_combo_c(std::move(*ccc)));
}

【问题讨论】:

  • 切片只能在复制到基类时发生。重新分配指针永远不会导致切片。

标签: c++ c++11 design-patterns smart-pointers unique-ptr


【解决方案1】:

unique_ptr&lt;T&gt; 存储 T*,而不是 T,因此在您的示例中您不会得到任何对象切片。

但是您确实存在内存泄漏,您分配的 ccc 对象不是任何人的 deleted,您正在从它构造一个新对象,但会泄漏原始对象本身。 manual_strategy::choose_combo 中的代码应该写成

auto ccc = std::make_unique<choose_combo_c>(p, gm);  // create a unique_ptr<choose_combo_c>
// do stuff with it
ccc->set_combo(available_combos.at(choice));
ccc->set_vp(available_combos_vp.at(choice));

// simply return it here, a unique_ptr<Igame_command>
// will be constructed from the unique_ptr<choose_combo_c>
return ccc;

【讨论】:

  • 如果我使用您的示例,同时避免您提到的泄漏,“自动”会创建一个类型为 choose_combo_c 的唯一指针,而不是 Igame_command。由于我在 ccc (set_combo(), set_vp() ) 上使用的函数来自派生类 choose_combo_c,所以我也不能立即将其声明为 Igame_command...
  • @Gathaspa 是的,有问题吗?
【解决方案2】:

你不能“移动”原始指针——无论如何,没有意义。您可以有意义地移动unique_ptr,但这不会将对象移动到任何地方,这只意味着该指向对象的所有权(作为分配的内存)移动到某个地方/其他人。

不过,这不是你正在做的;您的代码将您的临时对象移动到新分配的对象中(这会导致内存泄漏,如@Preatorian points out)。

无论对象的类型如何,什么是处理对象移动的更好方法?

使用指针实际上是一种合理的方式来进行对象“移动”,而不管它们的类型如何:你根本不会(或很少)实际移动任何对象,你只使用指向它们的指针。如果您想要类型遗忘,您可能无法使用类型化的unique_ptrs,但您可以使用带有自定义删除器的void *unique_ptr&lt;void&gt; 指针。

另一种选择是使用std::any,不带指针。如果对象很小,移动它们很便宜,如果它们很大,它们会将指针隐藏在里面,所以移动它们又很便宜。从某种意义上说,这种替代方案更安全,而不是分段违规和重新解释垃圾,如果您尝试错误的类型,您只会得到异常。

【讨论】:

    猜你喜欢
    • 2016-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-28
    • 2018-02-25
    • 2014-06-16
    • 2022-01-03
    • 1970-01-01
    相关资源
    最近更新 更多