ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

谢谢

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

A
Alexander Gessler

是的,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 来避免任何复制或移动(在容器提供的位置构造对象)。
K
Karl Nicoll

从 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<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
@Anakin - 是的,他们应该这样做,但前提是你复制。如果将 std::move()std::shared_ptr 一起使用,则原始共享指针可能会更改其指针,因为所有权已传递给向量。见这里:coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
G
Georg Fritzsche

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

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


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

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

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


它不取决于类型。它总是复制。如果它的指针是指针的副本
你俩都是对的。从技术上讲,是的,它总是复制。实际上,如果你将一个指向对象的指针传递给它,它会复制指针,而不是对象。安全地,您应该使用适当的智能指针。
是的,它总是在复制 - 但是,OP 所指的“对象”很可能是一个类或结构,所以我指的是它是否在复制“对象”取决于定义。不过措辞不好。
L
Liz Albin

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


r
rahmivolkan

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


关于指针向量的说明:vector > 比 vector 安全得多,并且 shared_ptr 是去年标准的一部分。
L
LemonPi

与 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 不应与现有对象一起使用,而应在适当位置构造一个新对象
N
NexusSquared

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

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

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

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

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


这不构成证明。如果对象没有被复制,你的最后一条语句将是未定义的行为并且可以打印你好。
正确的测试是在插入后修改两者之一。如果它们是同一个对象(如果向量存储了引用),则两者都将被修改。
显示不同的指针足以证明。在您的示例中 &vec[0] != &obj.其中 obj 被复制到 vec[0] 上。