用其他地方创建的对象填充std :: vector

时间:2020-06-10 15:38:25

标签: c++ vector move-semantics

我试图用在这样的函数中创建的对象填充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而又不破坏它们的最佳方法是什么?

6 个答案:

答案 0 :(得分:14)

如何破坏Foo使其次数超过创建的时间?

输出显示的破坏与破坏的数量一样多

            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

我认为可以复制Foo,但是不会触发复制构造函数。

每次将右值传递给构造函数。这就是为什么使用move构造函数而不是copy构造函数的原因。


用其他地方创建的对象填充std :: vector而不破坏它们的最佳方法是什么?

嗯,不要破坏您在其他地方创建的对象...但是通常应该避免这样做,因为这通常是内存泄漏。

如果在其他位置创建两个对象,在矢量中创建两个对象,则最终将创建4个对象。如果只需要两个对象,则可以直接在向量中创建对象,而在其他任何地方都可以。例如:

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

答案 1 :(得分:8)

这样做的时候

fooVector.push_back(createFoo());

第一个createFoo()创建一个临时Foo对象,这就是为什么您看到

Foo created!

然后,由于该对象是prvalue,因此将该对象“移动”到向量中。这就是为什么你看到

Foo moved

现在您在矢量中有一个对象,但是您还创建了一个临时对象,移动并不会摆脱该对象,它只是将其内部移动到矢量中的对象中。一旦对象超出范围,您仍然需要销毁该对象,这种情况发生在完整表达式的末尾,为您提供了

Foo destroyed

输出。

答案 2 :(得分:4)

当您创建std::move(obj)时,被移动对象的状态应该是可以被破坏的新状态。通常通过将对象保存的数据传输到新对象(将使用move ctor进行构造)来实现。最终我们所获取的物体也将被摧毁。

现在每个移动操作将构造一个新对象,并使旧对象处于待销毁状态,因此您将正确输出4个构造(默认情况下为2个ctor,两个为move ctor)和相应的4个销毁。 / p>

答案 3 :(得分:2)

移动语义的预期行为是,从对象移动不会破坏它,而是会破坏它,留下一个空壳。该外壳程序仍必须在其作用域的末尾处理,这意味着将照常调用其析构函数。该空对象应该处于“有效但未指定的状态”:您仍然可以执行任何没有前提条件的操作(例如,执行析构函数)。

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

这最终维护了以下规则:对于每种构造,无论构造类型如何都必须有相应的破坏。

答案 4 :(得分:0)

当您使用'}'时,在没有{}的情况下创建的所有局部变量都会被销毁,因此,当您拥有销毁器时,就会调用它。

答案 5 :(得分:0)

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

#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()之类的函数内部创建对象的本地实例。使用指针!