【问题标题】:Is std::vector copying the objects with a push_back?std::vector 是否使用 push_back 复制对象?
【发布时间】:2011-01-17 12:28:15
【问题描述】:

在对 valgrind 进行大量调查后,我得出结论,std::vector 会复制您想要 push_back 的对象。

这是真的吗?一个向量不能保留一个没有副本的对象的引用或指针?!

谢谢

【问题讨论】:

  • 这是C++的一个基本原理。对象是值。作业制作副本。除非您使用 *& 修改类型以创建指针或引用,否则两个变量引用同一对象是不可能的。
  • @DanielEarwicker push_back 实际上确实有参考。仅从签名尚不清楚它会复制。
  • @BrianGordon - 不是说它是!因此需要指导原则。即便如此,我们可以从push_back 的签名中推断出一些东西:它需要一个const&。要么将值丢弃(无用),要么有一种检索方法。因此,我们查看back 的签名,它返回普通的&,因此要么复制了原始值,要么默默地丢弃了const(非常糟糕:潜在的未定义行为)。因此,假设vector 的设计者是理性的(vector<bool> 无法承受),我们得出的结论是它会复制。

标签: c++ stl stdvector


【解决方案1】:

是的,std::vector<T>::push_back() 创建参数的副本并将其存储在向量中。如果要在向量中存储指向对象的指针,请创建 std::vector<whatever*> 而不是 std::vector<whatever>

但是,您需要确保指针引用的对象在向量持有对它们的引用时保持有效(使用 RAII 习语的智能指针可以解决问题)。

【讨论】:

  • 我还要注意,如果你使用原始指针,你现在负责清理它们。没有充分的理由这样做(无论如何我都想不到),您应该始终使用智能指针。
  • 也就是说,你不应该在 stl 容器中使用 std::auto_ptr,更多信息:why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers
  • 从 C++11 开始,如果参数是右值引用,push_back 将执行移动而不是复制。 (可以使用std::move() 将对象转换为右值引用。)
  • @tuple_cat 您的评论应该说“如果参数是右值”。 (如果参数是声明为右值引用的实体的名称,那么该参数实际上是一个左值并且不会被移出) - 检查我对“Karl Nicoll”答案的编辑,它最初犯了这个错误
  • 下面有一个answer,但要明确一点:由于C++11也使用emplace_back来避免任何复制或移动(在容器提供的地方构造对象)。跨度>
【解决方案2】:

从 C++11 开始,所有标准容器(std::vectorstd::map 等)都支持移动语义,这意味着您现在可以将右值传递给标准容器并避免复制:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

或者,您可以使用各种智能指针来获得几乎相同的效果:

std::unique_ptr 示例

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr 示例

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.

【讨论】:

  • 请注意,std::make_unique(令人讨厌)仅在 C++14 及更高版本中可用。如果要编译这些示例,请确保告诉编译器相应地设置其标准一致性。
  • 在5a中你可以使用auto pFoo =来避免重复;并且可以删除所有 std::string 强制转换(从字符串文字到 std::string 的隐式转换)
  • @user465139 make_unique 可以在 C++11 中轻松实现,因此对于使用 C++11 编译器的人来说,这只是一个小麻烦
  • @MM:确实。这是教科书的实现:template&lt;typename T, typename... Args&gt; unique_ptr&lt;T&gt; make_unique(Args&amp;&amp;... args) { return unique_ptr&lt;T&gt;{new T{args...}}; }
  • @Anakin - 是的,他们应该这样做,但前提是您复制。如果将std::move()std::shared_ptr 一起使用,则原始共享指针可能会更改其指针,因为所有权已传递给向量。见这里:coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
【解决方案3】:

是的,std::vector 存储副本。 vector 应该如何知道您的对象的预期生命周期是多少?

如果您想转移或共享对象的所有权,请使用指针,可能是像 shared_ptr(在 BoostTR1 中找到)这样的智能指针,以简化资源管理。

【讨论】:

  • 学习使用 shared_ptr - 他们完全按照您的意愿行事。我最喜欢的成语是 typedef boost::shared_ptr FooPtr;然后制作 FooPtrs 的容器
  • @pm100 - 你知道boost::ptr_vector 吗?
  • 我也喜欢用class Foo { typedef boost::shared_ptr&lt;Foo&gt; ptr; };来写Foo::ptr
  • @pm100 - shared_ptr 并不完全是一劳永逸。见stackoverflow.com/questions/327573stackoverflow.com/questions/701456
  • shared_ptr 如果您拥有共享所有权,则很好,但它通常被过度使用。当所有权明确时,unique_ptr 或 boost scoped_ptr 更有意义。
【解决方案4】:

std::vector 总是复制存储在向量中的任何内容。

如果您保留一个指针向量,那么它将复制指针,但不会复制指针指向的实例。如果您正在处理大型对象,您可以(并且可能应该)始终使用指针向量。通常,使用适当类型的智能指针向量有利于安全目的,因为否则处理对象生命周期和内存管理可能会很棘手。

【讨论】:

  • 它不依赖于类型。它总是复制。如果它的指针是指针的副本
  • 你说得对。从技术上讲,是的,它总是复制。实际上,如果您将一个指向对象的指针传递给它,它会复制指针,而不是对象。安全起见,您应该使用适当的智能指针。
  • 是的,它总是在复制 - 但是,OP 所指的“对象”很可能是一个类或结构,所以我指的是它是否在复制“对象”取决于定义。不过措辞不好。
【解决方案5】:

std::vector 不仅会复制您要推回的任何内容,而且集合的定义指出它将这样做,并且您不能在向量中使用没有正确复制语义的对象。因此,例如,您不要在向量中使用 auto_ptr。

【讨论】:

    【解决方案6】:

    与 C++11 相关的是 emplace 系列成员函数,它允许您通过将对象移动到容器中来转移对象的所有权。

    习惯用法是这样的

    std::vector<Object> objs;
    
    Object l_value_obj { /* initialize */ };
    // use object here...
    
    objs.emplace_back(std::move(l_value_obj));
    

    左值对象的移动很重要,否则它将作为引用或常量引用转发,并且不会调用移动构造函数。

    【讨论】:

    • emplace_back 不应与现有对象一起使用,而应在适当位置构造一个新对象
    【解决方案7】:

    如果您不想要副本;那么最好的方法是使用指针向量(或其他用于相同目标的结构)。 如果你想要副本;直接使用 push_back()。 你别无选择。

    【讨论】:

    • 关于指针向量的说明:vector > 比 vector 安全得多,并且 shared_ptr 是去年标准的一部分。
    【解决方案8】:

    为什么需要大量 valgrind 调查才能发现这一点! 只需用一些简单的代码证明给自己看,例如

    std::vector<std::string> vec;
    
    {
          std::string obj("hello world");
          vec.push_pack(obj);
    }
    
    std::cout << vec[0] << std::endl;  
    

    如果打印“hello world”,则该对象必须已被复制

    【讨论】:

    • 这不构成证明。如果没有复制对象,您的最后一条语句将是未定义的行为并且可以打印 hello。
    • 正确的测试是在插入后修改两者之一。如果它们是同一个对象(如果向量存储了引用),则两者都将被修改。
    • 显示不同的指针就足以证明。在您的示例中 &vec[0] != &obj.其中 obj 被复制到 vec[0] 上。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-27
    • 1970-01-01
    • 2013-10-15
    • 2021-06-18
    • 2023-03-21
    相关资源
    最近更新 更多