【问题标题】:C++ and standard containers: memory addresses of membersC++ 和标准容器:成员的内存地址
【发布时间】:2018-12-19 11:05:25
【问题描述】:

我目前对标准容器中的内存管理的理解有些困难,尤其是std::vector。

我很清楚std::vector 将在添加元素后调整大小,如果没有足够的保留空间,因此移动每个元素并更改内存中的地址。我现在的问题是:元素的成员变量会发生什么?

我的问题是基于我目前在std::vector 中管理我的游戏引擎的场景。场景由包含std::vector 的Scenes 的Scene 管理器类管理。添加场景如下所示:

    std::vector<Scene> scenes;
    Scene* active;
...
        Scene scene;
        scenes.emplace_back(scene);
        active = &scenes.back();

场景是堆栈分配的,离开方法后将被丢弃。为了将当前添加的场景暴露给外部,我存储了一个指向 std::vector 后面的指针,这是新插入的元素。

Scenes 包含各种成员,例如Light 类的实例。由于各种原因,我还向外部公开了指向这些元素的指针。我的问题是我试图在Scene 的构造函数中使用这些指针,这些指针是在Scene 管理器中构造的。在将对象添加到std::vector 之后,即使似乎没有调用Scene 构造函数,似乎也构造了一个新对象。 “活动”成员现在包含与我之前分配的实际 Scene 对象不同的内存地址。由于矢量需要调整大小,我认为我很清楚。

但是场景中的成员会发生什么?原来的Scene 将被破坏,活动元素获得另一个内存地址。这意味着由于内部调整大小,它指向一个全新的元素,并且该新元素具有新成员,并且在我的情况下是我想要使用的那些。

我的理解是否正确?

我的第二个问题包括我应该如何处理这样的情况:我想公开指向存储在 std::vector 中的对象成员的指针,但大小未知。我目前选择的方法效果很好,但我不确定这是否是正确的方法:

在Scene 类中有一个onActivate 事件方法,它将在整个调整大小和获取向量的插入元素完成后调用。当我切换活动指针时,该方法也会被调用。该方法获取指向场景成员的指针并传递它们。它看起来像这样:

void postConstruct() {
    std::cout << "postConstruct: " << &el << std::endl;
}

并将在 Scene 管理器中的正确位置调用,该管理器当前是 friend 到 Scene 类,因为这些事件不应暴露给外部世界。

    active->postConstruct();

这是正确的方法吗?

【问题讨论】:

  • 请记住,当std::vector 需要放大自身时,它可以重新分配已使用的内存。这意味着所有指向元素的迭代器和指针都变得无效并且不能再使用。因此,保存指向向量元素的指针并不是一个好主意。
  • 指向向量元素的指针的值旨在在调整向量大小后始终刷新,这是从外部访问向量元素的唯一方法。我计划稍后用地图替换矢量,但现在我对这种访问方法很好。
  • 我需要明确地跟踪一个元素。我不确定 stl 容器是否适合以后使用,但现在这是我的情况。
  • 通过“保留地址”,您是在问向量元素是否保持相同的地址?或者它们的指针成员在被移动/复制后是否保持相同的值?
  • "...移动每个元素并将地址保存在内存中。"不,我已经看到一个元素的内存地址没有被保留。我相信现有向量元素的索引是通过向量扩展保留的。即 push_back (超出当前容量)创建一个新的连续分配,并添加一个新索引。但是涉及到很多实现细节。示例:在 ubuntu 上,使用 g++,扩展(由后推引起)使连续内存加倍。

标签: c++ c++11 pointers vector stdvector


【解决方案1】:

如果std::vector 被调整大小,如果移动构造函数声明为noexcept,则元素将使用元素的移动构造函数移动,或者将使用元素的复制构造函数复制到新分配的位置。

重新分配后成员指针是否相同取决于插入元素的移动构造函数或复制构造函数的实现方式。

我建议使用索引而不是Scene* 来访问std::vector 中的元素,或者如果您想使用Scene*,请使用std::list

【讨论】:

  • 我也是这么想的。因此,由于我没有明确定义的复制或移动构造函数,因此似乎使用了默认的复制构造函数,它只会将成员变量的值复制到新的内存位置。这似乎是正确的?
【解决方案2】:

当vector 展开时,所有迭代器、指针和元素引用都将变为无效。 唯一定义的可以用无效指针或迭代器做的事情是用另一个值覆盖它,并且没有没有可以用无效引用做。即使比较该值与其他值也会使您的程序格式错误。

当你这样做时

Scene scene; 
scenes.emplace_back(scene); 
active = &scenes.back();

你有 两个 Scene 对象。一个是局部变量,另一个在向量中,是从scene复制的。我不确定您是否意识到这种区别,您可能只想要 one Scene 对象。要么所有Scenes 都住在scenes 中,要么你将其更改为std::vector&lt;Scene *&gt; 或std::vector&lt;std::reference_wrapper&lt;Scene&gt;&gt;。如果您执行后者,请确保在销毁值之前将其删除。

语言提供的复制构造函数将简单地复制每个成员的值,在指针的情况下,这往往是错误的。您可以为Scene 显式定义一个复制构造函数,以准确控制发生的情况,例如“深拷贝”指针成员。

class Copyable
{
    int* ptr;
public: 
    Copyable() : ptr(new int) {}
    ~Copyable() { delete ptr; }
    Copyable(const Copyable & other) : ptr(new int(*other.ptr)) {} // deep copy
    Copyable& operator=(const Copyable & other)
    {
        *ptr = *other.ptr;
        return *this;
    }
};

或者,您可以通过将复制构造函数定义为= deleted 来禁止复制Scenes

class UnCopyable
{
    int* ptr;
public: 
    UnCopyable() : ptr(new int) {}
    ~UnCopyable() { delete ptr; }
    UnCopyable(const UnCopyable & other) = delete;
    UnCopyable& operator=(const UnCopyable & other) = delete;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-01
    • 2011-12-28
    • 2021-02-16
    • 2018-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多