用别处创建的对象填充 std::vector

Filling std::vector with objects created elsewhere

我正在尝试使用在函数中创建的对象填充 std::vector,如下所示:

class Foo
{
public:
   Foo() { std::cout << "Foo created!\n"; }
   Foo(const Foo& other) { std::cout << "Foo copied!\n"; }
   Foo(Foo&& other) { std::cout << "Foo moved\n"; }
   ~Foo() { std::cout << "Foo destroyed\n"; }
};

static Foo createFoo() 
{
   return Foo();
}

int main()
{
   {
       std::vector<Foo> fooVector;
       fooVector.reserve(2);
       fooVector.push_back(createFoo());
       fooVector.push_back(createFoo());
       std::cout << "reaching end of scope\n";
   }
   std::cin.get();
}

输出:

Foo created!
Foo moved
Foo destroyed
Foo created!
Foo moved
Foo destroyed
reaching end of scope
Foo destroyed
Foo destroyed

为什么 Foo 被销毁的次数比创建的次数多?我不明白这里发生了什么,我以为 Foo 会被复制,但是没有触发复制构造函数。

用别处创建的对象填充 std::vector 而不销毁它们的最佳方法是什么?

How can Foo be destroyed more times than it is created?

输出显示的创建次数与显示的破坏次数完全一样:

            change -> cumulative total    
Foo created!    +1 -> 1
Foo moved       +1 -> 2
Foo destroyed   -1 -> 1
Foo created!    +1 -> 2
Foo moved       +1 -> 3
Foo destroyed   -1 -> 2
reaching end of scope
Foo destroyed   -1 -> 1
Foo destroyed   -1 -> 0 all objects that were created are now destroyed

I thought that Foo would be copied, but the copy constructor is not triggered.

每次将右值传递给构造函数时。这就是为什么使用移动构造函数而不是复制构造函数的原因。


What is the best way to fill a std::vector with objects created elsewhere without them being destroyed?

好吧,不要破坏您在别处创建的对象...但通常您应该避免这样做,因为这通常是内存泄漏。

如果您在别处创建两个对象并在向量中创建两个对象,那么您最终会创建 4 个对象。如果您只想要两个对象,那么例如直接将对象创建到向量中而不是其他任何地方。例如:

fooVector.emplace_back();
fooVector.emplace_back();

当你这样做时

fooVector.push_back(createFoo());

首先 createFoo() 创建一个临时 Foo 对象,这就是您看到

的原因
Foo created!

然后,该对象 "moved" 进入向量,因为它是纯右值。这就是为什么你看到

Foo moved

现在向量中有一个对象,但也有已创建的临时对象,移动并不会删除该对象,它只是将其内部移动到向量中的对象中。一旦对象超出范围,您仍然需要销毁该对象,这发生在完整表达式的末尾,给您

Foo destroyed

输出。

移动语义的预期行为是从一个对象移动不会破坏它,而是破坏它,留下一个空的 shell。 shell 仍然必须在其范围的末尾处理,这意味着将照常调用其析构函数。该空对象应该在 "valid but unspecified state" 中:您仍然可以执行任何没有先决条件的操作(例如执行析构函数)。

如果您为具有移动语义的类型编写析构函数,您需要考虑到您可能正在销毁这样一个空对象。在这种情况下,析构函数可能不会做太多工作,但这取决于您的用例。

这最终维护了一个规则,即对于每个构造,都必须有一个相应的破坏,无论是哪种构造

当你std::move(obj)时,移动对象的状态应该是一个可以被销毁的新状态。这通常是通过将对象持有的数据传输到新对象(将使用 move ctor 构造)来实现的。最后,我们获取其内容的对象也将被销毁。

现在每个移动操作都会构造一个新对象并使旧对象处于待销毁状态,因此您有正确的输出 4 个构造(默认为 2 个 ctor,两个为移动 ctor)和相应的 4 个销毁.

当你使用 '}' 时,在 {} 之间没有 malloc 创建的每个局部变量都会被销毁,所以当你有一个破坏者时,它就会被调用。

查看此输出 ( ͡° ͜ʖ ͡°)

#include <iostream>
#include <vector>
#include <memory>

class Foo
{
public:
    Foo() { std::cout << "Foo created!\n"; }
    Foo(const Foo& other) { std::cout << "Foo copied!\n"; }
    Foo(Foo&& other) { std::cout << "Foo moved\n"; }
    ~Foo() { std::cout << "Foo destroyed\n"; }
};

static Foo* createFoo()
{
    return new Foo();
}

int main()
{
    {
        std::vector<std::unique_ptr<Foo>> fooVector;
        fooVector.reserve(2);
        fooVector.emplace_back(createFoo());
        fooVector.emplace_back(createFoo());
        std::cout << "reaching end of scope\n";
    }
    std::cin.get();
}

如果您是 C++ 爱好者并且关心性能,那么在 createFoo() 等函数内部创建对象的本地实例很少会引起共鸣。使用指针!