【问题标题】:C++ composite reference to owner is corrupted when owner is moved移动所有者时,对所有者的 C++ 复合引用已损坏
【发布时间】:2023-03-11 12:20:02
【问题描述】:

我一整天都在做这件事,所以我希望我不会忘记任何重要的细节,但是就这样吧。我最初的目标是拥有一个封装了如何创建播放器的逻辑的播放器工厂。

看起来是这样的:

Player PlayerFactory::CreatePlayer() {

    Player constructee(id_generator++);

    // c++1x move semantics says this isn't a copy!
    return constructee;
}

在播放器中,有一个复合的 PlayerInputComponent 成员变量。这个 PlayerInputComponent 对象封装了处理玩家输入的逻辑,为此它需要一个指向实际玩家本身的引用/指针。很简单,它将对播放器的引用作为其构造函数的唯一参数。

当玩家对象被构造时,它的初始化列表将一个对自身的引用传递给 PlayerInputComponent 对象。下面是它的外观:

Player::Player(const unsigned int id) 
    : id(id), 
    input_component(*this)
{
...
}

我知道在初始化列表中取消引用 this 通常是一个坏主意,但我只是使用它在 PlayerInputComponent 对象中设置引用。这是构造函数:

PlayerInputComponent::PlayerInputComponent(Player &player) 
    : player_entity(player) { // set the reference, it is never used within the constructor body
}

无论出于何种原因,当播放器工厂返回它创建的播放器的本地副本时,引用就会出现乱码。我的意图是让在堆栈上创建的播放器实例被移动并分配给被调用者。如下所示:

auto player = player_factory.CreatePlayer();

在代码执行后,播放器的复合 PlayerInputComponent 对象对其播放器的引用被破坏。我知道当工厂将本地播放器对象返回给被调用者时会调用播放器的移动构造函数,但是播放器在 PlayerInputComponent 中的引用会发生什么?有没有更好的方法来解决这个问题?我喜欢在这种情况下使用引用与指针的语义含义,尽管我确实尝试使用指针并得到相同的结果。

谁能向我解释当播放器对象移出 CreatePlayer() 成员函数并分配给“自动播放器”时 PlayerInputComponent 对播放器的引用发生了什么?

为了完整起见,这里是对象的声明:

class PlayerInputComponent {

private:
    Player &player_entity;
    void HandleKeyboardInput();
public:
    //PlayerInputComponent(PlayerInputComponent &&other);
    //PlayerInputComponent(const PlayerInputComponent &other);
    PlayerInputComponent(Player &player);
    void Update();
};

这是播放器:

class Player : public Entity{
    friend class PlayerFactory;
private:
    const unsigned int id;

public:
    Player(const unsigned int id);

    // components
    PlayerInputComponent input_component;
};

我在下面重建了一个小示例,显示了确切的行为,它编译/演示了问题。谢谢!

class B;

class A  {
public:
    A(B& bb) : b(bb) { }

private:
    B &b;
};

class B {
public:
    B() : a(*this) { othercrap = othercrap2 = 0; }
    int othercrap;
    int othercrap2;
private:
    A a;
};

class BFactory {
public:
    B CreateB() {
    B temp;
        return temp;
    };
};

B start() {
    BFactory fac;
    auto bi = fac.CreateB();

    return bi;
}

int main(int argc, char *argv[]) {
    auto i = start();
}

【问题讨论】:

  • 您应该重新考虑整个“我喜欢引用与指针的语义含义”...T* constT& 之间的区别非常很少实际上。

标签: c++ reference move-constructor


【解决方案1】:

移动会使引用无效。 (引用的是存储位置,而不是对象)

请记住,移动会创建一个新对象(因此名称为 move constructor)。内容的所有权被转移,但对象实际上并未移动到内存中的新位置。新对象构建完成后销毁。

您应该定义Player 的复制和移动构造函数以正确绑定新对象中的引用。

【讨论】:

  • 我写了下面的移动构造函数 Player(Player &&other) : id(other.id), input_component(other.input_component) { };似乎没有正确传输参考,这不正确吗?
  • @shortstompcpp:嗯,这将input_component 的内部引用绑定到正在销毁的other。而是使用: id(other.id), input_component(*this)。如果有任何其他状态要复制/移动,甚至可能是input_component(*this, other.input_component)(并编写相应的构造函数)。
  • 啊哈!我现在知道了。完美运行!谢谢!我也明白为什么我正在做的事情没有可能的工作机会。
【解决方案2】:

我在您的示例中看不到任何移动语义。 CreateB 只会获得内部临时(temp),该内部临时(temp)将简单地复制到要分配给的临时返回对象(xvalue)上。您的引用会出现乱码,因为它们是简单地复制对已经销毁的本地临时对象的引用。

【讨论】:

  • 局部变量是return语句中的xvalue,所以应该移动。 class Player 有一个隐式默认的移动构造函数。但是移动与复制没有区别,在这两种情况下,新对象仍在创建中,并且引用处于悬空状态。
  • @BenVoigt 嘿,我听说过左值和右值,但什么是 xvalue?对我来说,因为他不使用 std::move,而且 temp 是一个普通的本地声明变量,它是一个左值......
  • 并且作为左值它会被复制。
  • 您需要了解 xvalues 才能理解移动。请参阅 C++11 标准的第 3.10 节。还有this question
  • temp 是一个左值。在 return 语句中,它也是一个 xvalue。它将被移动到实际的返回值,这也是一个xvalue,然后又会被移动到bibi是一个lvalue,在start()的return语句中变成了一个xvalue。它被移动到返回值,并且返回值(一个临时的,因此是xvalue)被移动到main()内的i变量。 “移动”是指用作传递给移动构造函数的源变量。
猜你喜欢
  • 2023-02-01
  • 2013-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-01
  • 1970-01-01
相关资源
最近更新 更多