【问题标题】:Is it safe to push_back 'dynamically allocated object' to vector?将“动态分配的对象”推回向量是否安全?
【发布时间】:2010-11-15 14:37:46
【问题描述】:

每当我需要将动态分配的对象添加到向量中时,我都会这样做:

class Foo { ... };

vector<Foo*> v;

v.push_back(new Foo);

// do stuff with Foo in v

// delete all Foo in v

它刚刚奏效,许多其他人似乎也在做同样的事情。

今天学习了vector::push_back 可以抛出异常。这意味着上面的代码不是异常安全的。 :-( 所以我想出了一个解决方案:

class Foo { ... };

vector<Foo*> v;
auto_ptr<Foo> p(new Foo);

v.push_back(p.get());
p.release();

// do stuff with Foo in v

// delete all Foo in v

但问题是新方法冗长乏味,而且我看到没有人这样做。 (至少不在我身边……)

我应该走新路吗?
或者,我可以继续使用旧方法吗?
或者,有没有更好的方法?

【问题讨论】:

  • 另请参阅:[为什么将 std::auto_ptr 与 STL 容器一起使用是错误的?][1] [1]: stackoverflow.com/questions/111478/…
  • @Martin:实际上在这里使用auto_ptrvector 没有问题(我的意思是,特定于他们的交互),因为我们正在谈论的不是vector&lt;auto_ptr&lt;T&gt;&gt;关于。
  • @Matthieu:你是对的。然后我再次避免存在问题,尽管我的链接可能确实暗示了它:-)

标签: c++ vector exception-safe


【解决方案1】:

如果您只关心此操作的异常安全性:

v.reserve(v.size()+1);  // reserve can throw, but that doesn't matter
v.push_back(new Foo);   // new can throw, that doesn't matter either.

vector 负责释放其内容所指向的对象的问题是另一回事,我相信你会得到很多关于此的建议 ;-)

编辑:嗯,我打算引用标准,但实际上我找不到必要的保证。我正在寻找的是 push_back 不会抛出,除非(a)它必须重新分配(我们知道它不会因为容量),或者(b) T throws 的构造函数(我们知道它不会因为 T 是指针类型)。听起来合理,但合理!= 保证。

所以,除非在这个问题上有一个有益的答案:

Is std::vector::push_back permitted to throw for any reason other than failed reallocation or construction?

这段代码依赖于没有做任何“想象力”的实现。如果做不到这一点,您的问题解决方案可以模板化:

template <typename T, typename Container>
void push_back_new(Container &c) {
    auto_ptr<T> p(new T);
    c.push_back(p.get());
    p.release();
}

然后使用不会太繁琐:

struct Bar : Foo { };

vector<Foo*> v;
push_back_new<Foo>(v);
push_back_new<Bar>(v);

如果它真的是一个工厂函数而不是new,那么你可以相应地修改模板。但是,在不同情况下传递大量不同的参数列表会很困难。

【讨论】:

  • 是的,对于这个问题,我关心的只是异常安全。谢谢!
【解决方案2】:

您的新方法更多异常安全,但有一个原因是您在其他任何地方都看不到它。

指针的vector 仅拥有指针,它不表示指向对象的所有权。您实际上是在向不“想要”所有权的对象释放所有权。

大多数人会使用vectorshared_ptr 来正确表达所有权或使用boost::ptr_vector 之类的东西。这两种方法中的任何一种都意味着您不必显式地delete 要存储其指针的对象,这些对象容易出错,并且在程序的其他点可能会出现异常“危险”。

编辑:插入ptr_vector 时仍需非常小心。不幸的是,push_back 采用原始指针提供了强有力的保证,这意味着插入成功或(有效地)什么都没有发生,因此传入的对象既不会被接管也不会被销毁。按值获取智能指针的版本定义为在调用强保证版本之前调用.release(),这实际上意味着它可能会泄漏。

shared_ptr 中的vectormake_shared 一起使用更容易正确使用。

【讨论】:

  • “定义为在调用强保证版本之前调用 .release()”——这太离谱了。不是吗?
  • @Steve:同意。 (对我而言)通过非const 引用并且仅在所有权成功转移后仅使用release 引用auto_ptr 会更有意义。
  • 听起来不错,与shared_ptr(auto_ptr) 构造函数相同。对于这种用法,我也同意按值获取它并释放或销毁,相当于 shared_ptr(T*) 构造函数,但应用于 auto_ptr 参数的指针内容。
  • @Steve:是的,最重要的是不要泄露。但是,如果分配失败,让您选择对您未能“放弃”的对象执行某些操作可能与大多数程序无关,因为它们将迅速进入“所有赌注”状态。
【解决方案3】:

这样做的首选方法是使用智能指针容器,例如,std::vector&lt;std::shared_ptr&lt;Foo&gt; &gt;std::vector&lt;std::unique_ptr&lt;Foo&gt; &gt;shared_ptr 也可以在 Boost 和 C++ TR1 中找到;std::unique_ptr 是有效的仅限于 C++0x)。

另一种选择是使用拥有动态对象的容器,例如 Boost Pointer Containers 库提供的那些容器。

【讨论】:

    【解决方案4】:

    您的程序对内存短缺的适应能力如何?如果你真的关心这个,你也必须为new 投掷做好准备。如果你不打算处理这个问题,我不会担心跳过push_back 箍。

    在一般情况下,如果您的内存不足,则程序可能已经存在无法解决的问题,除非它专门设计为在受限空间(嵌入式系统)中永久运行 - 在这种情况下,您必须关心所有这些问题案例。

    如果这适用于您,您可能会面临漫长的代码审查和重新测试周期。不过,我的猜测是,您可以在这里遵循团队的做法。

    正如其他人指出的那样,使用 vector 存储原始指针有其自身的问题,并且此站点和其他答案中有大量材料可以指导您使用更安全的模式。

    【讨论】:

    • @Chubsdad - 问题被标记为“异常安全”,所以我认为这是相关的。建议的修复都与vector 中的原始指针使用有关,但没有解决new 抛出时会发生什么。
    • 如果new Foo 抛出,则没有分配内存。如果vector 重新分配抛出,我们只是丢失了new Foo 分配的内存地址。如果向量重新分配可能会失败,而不会在内存中“短缺”,如果它增长太多或者您的内存太碎片化。
    • @Matthieu - 我知道会发生什么。我只是想解决所有情况下与内存相关的异常安全的更广泛问题,而不是这里发布的几行代码。我想我们都同意这并不容易。
    猜你喜欢
    • 1970-01-01
    • 2014-05-20
    • 2016-04-26
    • 2015-04-26
    • 2017-02-17
    • 2020-03-09
    • 2020-01-10
    • 2021-07-08
    • 1970-01-01
    相关资源
    最近更新 更多