std :: vector是否使用push_back复制对象?


168

经过对valgrind的大量研究后,我得出的结论是std :: vector可以复制您要push_back的对象。

这是真的吗?如果没有副本,向量不能保留对象的引用或指针。

谢谢


20
这是C ++的基本原理。对象就是价值。作业进行复制。除非使用*或修改&指针的类型或使其成为指针或引用,否则引用同一对象的两个变量是不可能的。
Daniel Earwicker 2010年

8
@DanielEarwicker push_back实际上引用了。仅凭签名尚不清楚它是否会复制。
Brian Gordon

3
@BrianGordon-不说是!因此,需要指导原则。即使这样,我们也可以从的签名中得出一些结论push_back:它需要一个const&。它要么丢弃该值(无用),要么存在一种检索方法。因此,我们查看的签名back,它返回plain &,因此要么复制了原始值,要么将其const默默抛弃(非常糟糕:可能未定义的行为)。因此,假设的设计者vector是理性的(vector<bool>而不是承受),我们得出结论,它制作了副本。
Daniel Earwicker 2014年

Answers:


183

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

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


我还要指出的是,如果您使用原始指针,则现在有责任清理它们。这样做没有充分的理由(无论如何我都不会想到),您应该始终使用智能指针。
Ed S.

1
就是说,您不应该在stl容器中使用std :: auto_ptr,以获取更多信息:为什么错误地使用stdauto-ptr和standard-containers
OriginalCliche

24
从C ++ 11开始,push_back如果参数是右值引用,则将执行移动而不是复制。(可以使用std::move()
将该

2
@tuple_cat,您的注释应显示“如果参数是右值”。(如果参数是声明为右值引用的实体的名称,则该参数实际上是左值,并且不会从中移出)-检查我对“卡尔·尼科尔”答案的编辑,这最初导致该错误
MM

下面有一个答案,但要明确:由于C ++ 11还用于emplace_back避免任何复制或移动(在容器提供的适当位置构造对象)。
Wormer

34

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

如果要转移或共享对象的所有权,请使用指针,可能会使用智能指针shared_ptr(如BoostTR1中的指针)来简化资源管理。


3
学习使用shared_ptr-他们完全按照您的意愿做。我最喜欢的成语是typedef boost :: shared_ptr <Foo> FooPtr; 然后制作FooPtrs容器
2010年

3
@ pm100-你知道boost::ptr_vector吗?
曼努埃尔

2
我也喜欢用class Foo { typedef boost::shared_ptr<Foo> ptr; };只是写Foo::ptr
罗珀·琼斯

2
@ pm100- shared_ptr并非完全是火和忘了。参见stackoverflow.com/questions/327573stackoverflow.com/questions/701456
Daniel Earwicker,2010年

2
如果您拥有共享所有权,则shared_ptr很好,但是通常会过度使用它。当所有权明确时,unique_ptr或boost scoped_ptr会更有意义。
Nemanja Trifunovic

28

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

// 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.

2
注意std::make_unique(恼人地)仅在C ++ 14及更高版本中可用。如果要编译这些示例,请确保告诉编译器相应地设置其标准一致性。
Laryx Decidua

在5a中,您可以使用auto pFoo =以避免重复;并且std::string可以删除所有类型转换(从字符串文字到的隐式转换std::string
MM

2
@ user465139 make_unique可以轻松地在C ++ 11中实现,因此对于那些坚持使用C ++ 11编译器的人来说只是一个小麻烦
MM

1
@MM:的确如此。这是教科书的实现:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua

1
@Anakin-是的,他们应该这样做,但前提是您要复制。如果std::move()与with一起使用std::shared_ptr,则自所有权转移给向量以来,原始共享指针可能会更改其指针。看到这里:coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll

15

std :: vector始终复制该向量中存储的内容。

如果保持指针向量,则它将创建指针的副本,但不会复制指针所指向的实例。如果要处理大型对象,则可以(并且应该)始终使用指针向量。通常,为了安全起见,使用适当类型的智能指针向量会很好,因为否则处理对象生存期和内存管理可能会很棘手。


3
它不取决于类型。它总是复制。如果它的指针是指针的副本
pm100,2010年

你们都是对的 从技术上讲,是的,它始终会复制。实际上,如果将指针传递给该对象,它将复制该指针,而不是对象。安全地,您应该使用适当的智能指针。
史蒂文·苏迪特

1
是的,它始终在复制-但是,OP所指的“对象”很可能是类或结构,因此我指的是它是否复制“对象”取决于定义。不过措辞很差。
里德·科普西

3

std :: vector不仅会复制您要回退的内容,而且集合的定义表明它将这样做,并且您可能在向量中没有正确的复制语义的情况下使用对象。因此,例如,您不在向量中使用auto_ptr。


2

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

使用的成语看起来像

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

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


0

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


1
关于指针向量的说明:vector <shared_ptr <obj>>比vector <obj *>安全得多,shared_ptr截至去年已成为标准的一部分。
rich.e 2012年

-1

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

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

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


4
这不构成证明。如果未复制对象,则您的最后一条语句将是未定义的行为,并且可能会打个招呼。

4
正确的测试是在插入后修改两个之一。如果它们是同一个对象(如果向量存储了引用),则两者都将被修改。
Francesco Dondi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.