用已创建的对象填充std::vector

12

我正在尝试使用函数创建的对象填充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,并且不让其中的对象在被添加后立即被销毁?这些对象是在其他地方创建的。


3
你所移动的物品最终也必须被销毁。 - UnholySheep
“没有它们被销毁?”——为什么这是个问题?你的代码已经有了明确定义的行为,不清楚你为什么需要其他东西。 - UnholySheep
6个回答

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会被复制,但复制构造函数没有被触发。

每次将rvalue传递给构造函数时,移动构造函数会被使用而不是复制构造函数。


在不销毁从其他地方创建的对象的情况下,填充std::vector的最佳方法是什么?

最好的方法是不要销毁您在其他地方创建的对象...但通常应避免这样做,因为这通常会导致内存泄漏。

如果您在其他地方创建了两个对象,并且在向量中创建了两个对象,则最终将创建4个对象。如果您只想要两个对象,例如可以直接将对象创建到向量中而不是任何其他地方。像这样:

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

感谢您清晰的解释。我现在明白了这个操作,之前我还不理解 :) - MrEighteen
@MrEighteen,我对你的问题又添加了一个澄清。 - eerorika

8

当你进行操作时

fooVector.push_back(createFoo());

createFoo()首次创建了一个临时的Foo对象,这就是你看到这个信息的原因。

Foo created!

然后,由于它是一个prvalue,该对象被“移动”到向量中。这就是为什么你看到的。
Foo moved

现在你在向量中有了一个对象,但你同时也创建了一个临时对象。移动并不会消除该对象,而是将其内部内容移动到向量对象中。当它超出作用域时仍需要销毁该对象,这将在完整表达式的结尾发生。

Foo destroyed

输出。


4
当你执行 std::move(obj)时,被移动对象的状态应当是一个可以被销毁的新状态。通常情况下,这是通过将该对象持有的数据传输到一个新对象(使用移动构造函数进行构造)来实现的,最后我们拿到内容的对象也会被销毁。
现在每次移动操作都会构造一个新对象,并使旧对象处于可以被销毁的状态,因此你会得到正确的输出结果:4个构造(2个默认构造函数和2个移动构造函数),以及相应的4个销毁。

2
移动语义的预期行为是移动对象不会销毁它,而是将其清空,留下一个空壳。这个空壳在其作用域结束时仍然需要被处理,这意味着它的析构函数将会被调用,就像平常一样。这个空对象应该处于“有效但未指定状态”,你仍然可以执行没有前提条件的任何操作(例如执行析构函数)。
如果你为具有移动语义的类型编写析构函数,你需要考虑到可能会销毁这样一个空对象。在这种情况下,析构函数可能不会做太多的工作,但这取决于你的使用情况。
这最终保持了这样一个规则:对于每个构造,都必须有相应的销毁,无论构造的类型如何。

1
“那个空对象应该处于“有效但未指定状态”,唯一允许的操作是调用析构函数。” 我不记得这是真的。例如,您可以从字符串中移动,然后将其重置为 "" - Asteroids With Wings
是的,任何没有先决条件的操作都可以使用已移动的对象。我的经验法则是,如果我可以对默认构造的实例执行操作,那么我也可以对已移动的对象执行相同的操作。 - NathanOliver

0

当你使用 '}' 时,所有在 {} 中创建的本地变量(未使用 malloc)都会被销毁,因此当你有一个析构函数时,它就会被调用。


销毁器被称为析构函数。 - BDL

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()。使用指针!

这不是好的建议。函数正在返回一个临时对象,所以完全没问题。至少,建议使用unique_ptr代替裸指针。 - cigien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接