我有以下代码,其中未使用new,但调用了std :: move()。
#include <iostream>
#include <vector>
#include <utility>
class Animal {
public:
Animal() { std::cout << "Animal()" << std::endl; };
~Animal() { std::cout << "~Animal()" << std::endl; };
virtual void eat() { std::cout << "Animal is eating" << std::endl; };
};
class Bear : public Animal {
public:
Bear() { std::cout << "Bear()" << std::endl; };
~Bear() { std::cout << "~Bear()" << std::endl; };
void eat() { std::cout << "Bear is eating" << std::endl; };
};
class Zebra : public Animal {
public:
Zebra() { std::cout << "Zebra()" << std::endl; };
~Zebra() { std::cout << "~Zebra()" << std::endl; };
void eat() { std::cout << "Zebra is eating" << std::endl; };
};
int main()
{
Bear bear;
Zebra zebra;
std::vector<Animal*> animalsp;
animalsp.push_back(&bear);
animalsp.push_back(&zebra);
for (auto i : animalsp) {
i->eat();
}
std::vector<Animal> animals;
animals.push_back(std::move(bear));
animals.push_back(std::move(zebra));
for (auto i : animals) {
i.eat();
}
return 0;
}
我得到以下输出:
Animal()
Bear()
Animal()
Zebra()
Bear is eating
Zebra is eating
~Animal()
Animal is eating
~Animal()
Animal is eating
~Animal()
~Animal()
~Animal()
~Zebra()
~Animal()
~Bear()
~Animal()
我原本期望对~Animal()
析构函数的调用次数减少。我还发现调用~Animal()
的时间是出乎意料的。我希望std::move()
调用可能会触发这个,但是在函数调用输出之前这样做。我在ideone和VS2015上得到了相同的输出。我错过了什么?
答案 0 :(得分:4)
移动并不会破坏对象,它只会将其状态标记为可丢弃。所以这一举动不会造成破坏(除非通过推回重新分配向量!)
自动存储对象(比如在函数中声明为locals)在其作用域的末尾被销毁。这就是为什么你最终得到所有这些析构函数的原因。
当矢量被摧毁时,它会摧毁存储在其中的动物(这些动物不是熊或斑马,只是动物,因为你要求它们按值存储,这意味着熊被切掉了) 。这是析构函数的另一个来源。
for (auto i:animals)
循环还会在向量中创建每个动物的副本,并将其销毁。更多的析构垃圾邮件。
Animal()
Bear() // Bear bear; line
Animal()
Zebra() // Zebra zebra; line
Bear is eating
Zebra is eating // first loop
~Animal() // reallocation of vector during push_backs
Animal is eating // second loop body
~Animal() // first i copy destroyed in second loop
Animal is eating // second loop body
~Animal() // second i copy destroyed in second loop
~Animal()
~Animal() // vector destroyed
~Zebra()
~Animal() // zerbra destroyed
~Bear()
~Animal() // bear destroyed
如果事情需要2行,我评论了第2行。
答案 1 :(得分:2)
for (auto i : animals)
正在创建向量中的对象副本。这可能是您看到的许多析构函数调用的来源。在这种情况下,通常最好通过引用传递,如:
for (auto& i : animals)
答案 2 :(得分:1)
即使使用移动语义,您也有多个在该范围结束时被销毁的对象。移动语义的实现是这些对象的内部实现。
示例:
#include <iostream>
class Resource
{
public:
int data = 0;
Resource()
{
std::cout << "Allocate resource\n";
data = 1;
}
Resource(Resource&& other)
: data(other.data)
{
std::cout << "Transfer resource\n";
other.data = 0;
}
Resource& operator = (Resource&& other)
{
std::cout << "Transfer resource\n";
data = other.data;
other.data = 0;
return *this;
}
Resource(const Resource&) = delete;
Resource& operator = (const Resource&) = delete;
~Resource()
{
if(data)
std::cout << "Deallocate resource\n";
}
};
int main(int, char**) {
Resource a;
Resource b(std::move(a));
return 0;
// Destruction of a and b follows.
}
注意:持有数据的对象不是移动语义的关注点,而是数据本身。
输出:
Allocate resource
Transfer resource
Deallocate resource
答案 3 :(得分:1)
很容易误解std :: move和rvalue引用的实际机制。使用右值引用构造对象时,预期(但绝不是契约)行为是接收构造函数将控制引用持有的任何动态内存,然后更改引用对象以反映它的事实不再控制任何动态内存。
这绝不意味着传入的右值将立即被丢弃。无论是否移动了析构函数,它仍然会被调用。关键的区别在于析构函数将能够检测到对象已被移动,因此没有调用释放内存。
当然,C ++是一种非常强大的语言,因此受到滥用。您可以在rvalue引用构造函数或析构函数中执行任何操作。这只是最常见的模式,因为malloc和free非常昂贵,所以尽可能少地调用它们就是好的设计。