【问题标题】:Questions on memorybehavior of vectors关于向量的记忆行为的问题
【发布时间】:2017-03-14 10:55:07
【问题描述】:

我最近对std::vectors的内存(取消)分配有点困惑

假设我得到了整数的法线向量: std::vector<int> intv; 当我 push_back 一些 int 时,它会随着时间而增长。当我离开函数的作用域(即)时,它会在不需要额外调用的情况下被释放。

太好了。再举一个例子:

struct foo_t{
    std::string bar:
    unsigned int derp;
}
void hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
}

好的。当我调用hurr() 时,向量被创建,foo_t 实例被创建,实例被填充并被推送到向量。所以当我离开函数时,向量被释放,内容(这里是foo_t)也被释放?

下一个例子:

struct foo_t{
    std::string bar:
    unsigned int derp;
}
std::vector<foo_t> hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
    return foov;
}

据我了解,向量及其内容存在于堆栈中,堆栈(最终)会被时间覆盖,而我返回的向量及其内容将毫无用处。或者它实际上是否返回了向量的副本及其内容的副本(如果它不是 POD,则需要内容数据类型的 Copy-Constructor)?

还有一些显而易见的事情:

struct foo_t{
    std::string bar:
    unsigned int derp;
}
std::vector<foo_t*> hurr(){
    std::vector<foo_t*> foov;
    foo_t foo = new foo_t;
    foo->bar = "Sup?";
    foo->derp = 1337;
    foov.push_back(foo);
    return foov;
}

现在我必须手动迭代向量,删除它的内容,然后我可以安全地让向量超出范围,对吧?

【问题讨论】:

  • “现在我必须手动迭代向量,删除它的内容,然后我可以安全地让向量超出范围,对吧?” 是的。
  • "现在我必须手动迭代向量,删除其内容,然后我可以安全地让向量超出范围,对吗?"这将使返回向量中的指针无效。除非那是你想要的,而且很可能不是,否则你不应该这样做。
  • @molbdnilo 取决于现在应该何时发生。
  • @πάνταῥεῖ 我知道它发生在函数返回之前:“遍历向量,删除其内容然后 [...]超出范围”。
  • @πάνταῥεῖ @molbdnilo “现在”是在我使用 hurr() 返回的向量之后,不再需要它了。

标签: c++ c++11 stl stdvector


【解决方案1】:

这个例子:

struct foo_t{
    std::string bar;
    unsigned int derp;
};
void hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
}

hurv() 完成后,foovfoo 都被释放。

std::vector<foo_t> hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
    return foov;
}

hurr() 的结果std::vector&lt;foo_t&gt; 是有效的,其中包含 1 个 foo_t 并且它是有效的。 return foov; 可以调用 std::vector&lt;foo_t&gt; 的复制构造器,并且它可以不制作该副本,请参阅 copy elision

不管怎样,从 C++11 开始,你可以这样写:

struct foo_t{
    std::string bar;
    unsigned int derp;
    // we will copy the string anyway, pass-by-value
    foo_t(std::string bar_, unsigned int d_)
        : bar(std::move(bar_)), derp(d_) {}
};
std::vector<foo_t> hurr(){
    std::vector<foo_t> foov;
    // This is better, in place construction, no temporary
    foov.emplace_back("Sup?", 1337);
    // This require a temporary
    foov.push_back(foo_t{"Sup?", 1337});
    return foov;
}

并且,对于最后一个示例,是的,您必须手动迭代向量,删除其内容,然后当您不再想使用时,我可以安全地让向量超出范围 hurr() 的结果,(不在hurr() 中)

【讨论】:

  • 您错过了emplace_back 的要点。关键是就地建造,避免临时的需要。你应该这样做emplace_back("sup", 1337)
  • @bolov 其实不能用emplace_back("sup", 1337) 编译,emplace_back 需要构造函数吧?
  • 是的,但在这种情况下,emplace_back 将与push_back 执行相同的操作,即将一个临时对象移动到向量中。
  • @Danh 是的,它可以而且应该这样使用:emplace_back("sup", 1337)。是的,它需要一个构造函数。它使用value_type 的适当构造函数就地构造对象。将其视为获取一些参数,然后将这些参数传递给value_type 的构造函数。如果你传递foo_t{"Sup?", 1337}那么你已经创建了一个临时的然后emplace_back基本上调用了value_type的复制或移动ctor,这与push_back相同。
  • 我喜欢这里使用 c++11 的构造函数和结构的复制构造函数。无论如何,如果我将构造函数添加到结构中,结构和所有公共类之间是否有区别?哪个更可取?
【解决方案2】:
foov.push_back(foo);

实际上,您构造了一个foo_v 并将其推回,这实际上创建了一个新的foo_v 并以foov 作为参数调用了复制构造函数。如果您想避免这种情况,请使用 emplace_back

return foov;

编译器可以使用返回值优化来优化它。请参阅this short program 我以在 coliru 上运行为例。参考this question中的其他优秀答案。

std::vector<foo_t*> foov;
/* add elements to foov with new */

现在我必须手动迭代向量,删除它的内容,然后我可以安全地让向量超出范围,对吧?

是的,你知道。出于同样的原因

int* a = new int();

delete a; 死后不会delete a;

【讨论】:

    【解决方案3】:

    所以当我离开函数时,向量被释放,并且 内容(这里是一个foo_t)也被释放了?

    是的。如果foo_t 有非平凡的析构函数,它就会被调用。

    或者它实际上是否返回了向量的副本及其副本 内容(需要内容数据类型的复制构造函数,如果它 不是 POD)?

    是的,在这种情况下它返回一个副本。现代编译器可能会为 std::vector 调用复制构造函数,而后者又会为每个元素调用包含类型的复制构造函数。 C++17 引入了保证返回值优化 (RVO),因此不会调用向量的复制构造函数。不过,如果你设置了高优化级别,现代编译器也可能会使用 RVO。

    现在我必须手动遍历向量,删除它的内容 然后我可以安全地让向量超出范围,对吗?

    是的,你是对的。如果您不想手动迭代,请考虑使用智能指针。

    【讨论】:

    • 在我的test program 中,-O0 中的 g++ 实际上 似乎使用了 RVO。 *edit: * 我实际上并没有在那个程序中编译-O0,但是如果你指定它,你会得到相同的结果。
    • 智能指针的使用是我遗漏的问题的另一个示例。
    • 即使没有复制省略,也会使用move-constructor(如果包含的类型是nothrow-moveable)
    猜你喜欢
    • 1970-01-01
    • 2021-08-05
    • 1970-01-01
    • 1970-01-01
    • 2013-01-05
    • 1970-01-01
    • 2023-02-04
    • 2021-12-17
    • 1970-01-01
    相关资源
    最近更新 更多