C++ 构造函数触发析构函数

C++ A constructor triggers a destructor

本文关键字:析构函数 构造函数 C++      更新时间:2023-10-16

我正在尝试使用向量和类制作一个Monster生成器,以允许用户根据需要创建和删除Monster对象。但是,当我在代码中调用Monster的构造函数时,它也会立即触发析构函数,从而从向量容器中删除Monster

这是我的代码片段,它创建和销毁Monster对象:

while (p == 't')
{
cout << "Generate (g) or remove (u) a monster?" << endl;
cin >> p;
while (p == 'u' && Monster::n == 0)
{
cout << "No monsters to remove." << endl;
cout << "Generate (g) or remove (u) a monster?" << endl;
cin >> p;
}
while (p != 'g' && p != 'u')
{
cout << "Error. Follow the instructions." << endl;
cout << "Generate (g) or remove (u) a monster?" << endl;
cin >> p;
}
if (p == 'g')
{
{
vec.push_back(Monster());
vec[Monster::n].print();
}
}
if (p == 'u')
{
vec.~Monster();
}
cout << "Continue (t) or finish (n)?" << endl;
cin >> p;
while (p != 't' && p != 'n')
{
cout << "Error. Follow the instructions?" << endl;
cout << "Continue (t) or finish (n)?" << endl;
cin >> p;
}

这是Monster的构造函数:

Monster::Monster()
{
ostringstream buffer;
buffer << Monster::n;
string name = buffer.str();
Monster::setname(name);
Monster::settype(MonsterType(rand() % 7 + 1));
Monster::setattack(rand() % 25);
Monster::sethealth(rand() % 15);
cout << endl << "Monster generated." << endl << endl;
Monster::n++;
}

这是析构函数:

Monster::~Monster()
{
cout << "Monster removed." << endl << endl;
Monster::n--;
}

当我输入字符'g'时,它会按预期创建一个Monster对象,但随后立即销毁它,甚至在使用print()方法打印其统计信息之前。这意味着我的代码没有按预期运行,因为它永远不会允许正确创建或销毁Monster对象。

有人可以帮我了解这里发生了什么吗?

编辑:

好的,我能够通过制作另一个构造函数来解决这个问题。

就是这样:

Monster::Monster(const Monster & mon)
{
Monster::name = mon.name;
Monster::type = mon.type;
Monster::attack = mon.attack;
Monster::health = mon.health;
cout << endl << "Monster created (cpy)." << endl << endl;
Monster::n++;
}

我编辑了用户决定生成以下内容Monster的点:

{
vec.push_back(Monster());
vector<Monster> veccpy = {};
veccpy.push_back(Monster(vec[Monster::n-1]));
vec.back().print();
}

当我打开程序时,它会吐出这样的东西:

创造的怪物

怪物创建(cpy)

怪物被移除

怪物创建(cpy)

怪物创建(cpy)

怪物被移除

怪物的统计数据

现在,奇怪的是,即使它如此奇怪地使用这些构造函数,该程序也按照我的预期工作。n按预期迭代,我可以按预期删除或添加Monster,并且Monster统计信息将按预期显示。

有人可以指出我在这里做错了什么吗?

在这一行中:

vec.push_back(Monster());

您正在创建 Monster 类型的临时对象,该对象正在复制到vec中。因为它是一个临时对象,所以它将在语句以分号结尾后立即销毁。复制到vec后将被销毁。

vec.~Monster();

这肯定不会编译,如果你想清除你的向量,那就用vec.clear()

在编写代码时Monster::setname(name);大多数程序员会认为你在设置一个静态变量。我希望您不要将monster的名称保留为静态变量。


您需要在代码中修复的另一件事是确保它遵守三规则(就像在注释中一样)。您的Monster::n变量是一种由 Monster 类管理的资源,要正确管理它,您需要在所有可能创建/破坏 Monster 类实例的构造函数和运算符中适当修改它。下面没有用长文本描述它,下面是一个示例代码,它显示了在您的代码中调用的构造函数/运算符:

http://coliru.stacked-crooked.com/a/9184f137b7a90995

struct Monster {
Monster() { std::cout << n << ":" << __PRETTY_FUNCTION__ << "n"; n++; }
~Monster() { std::cout << n << ":" << __PRETTY_FUNCTION__ << "n"; n--; }
Monster(const Monster&) { std::cout << n << ":" << __PRETTY_FUNCTION__ << "n"; n++; }
Monster& operator=(const Monster&) { std::cout << n << ":" << __PRETTY_FUNCTION__ << "n"; n++; return *this; }        
static int n;    
};
int Monster::n;
int main()
{
{
std::vector<Monster> vec;
std::cout << "-------------- STARTn";
vec.push_back(Monster());    
std::cout << "-------------- ENDn";
}
std::cout << Monster::n; // should output 0
}
-------------- START
0:Monster::Monster()                  // temporary created
1:Monster::Monster(const Monster&)    // vector element is copy constructed
//  from temporary here you have missed Monster::n++
2:Monster::~Monster()                 // temporary monster destroyed
-------------- END
1:Monster::~Monster()                 // this is a vector being destroyed
0                                     // this should always be 0, otherwise
//  still something is missing.
vec.push_back(Monster());

这将创建一个临时对象,一个Monster类的实例,并将其传递给向量的push_back()方法。

push_back()将获取其参数并将其复制到向量中。由于向量拥有其对象,这将涉及构造Monster类的另一个实例。

push_back()返回它传递的临时对象,因为参数将被销毁,从而调用对象的析构函数。这就是为什么你在这里看到一个析构函数被调用。

请注意,向向矢量添加对象时,矢量可以随时选择重新分配其内容。这本质上将涉及在向量中复制构造类的新实例,然后销毁旧实例。

经验 教训

花一些时间阅读你的C++书,了解智能指针是如何工作的,并将智能指针放在你的向量中,而不是对象实例中,以避免由于临时而引起的不必要的副作用。

您还可以实现移动构造函数作为替代解决方案,但使用智能指针可能会更容易。

第二种选择是emplace_back(),而不是push_back()新实例。但是,这并不能完全解决您的问题。见上文"重新分配"。

此外,使您的班级符合 3 规则也不会有什么坏处。你问这个问题的真正根本原因可能是因为它目前不是。

你所看到的是意料之中的,它正在发生,因为你已经蝑视了vec,就像std::vector<Monster>一样。声明包含类对象的向量是什么意思Monster

现在矢量的工作方式是,它们需要使所有这些 Monster 类对象保持活动状态,除非您将它们从矢量中删除。这意味着,这些Monstors对象的范围应该是等效的,直到对象仍然是向量。

话虽如此,现在如果你看这行代码vec.push_back(Monster());,你声明的新 Monster 对象帽子将具有范围,直到if中的大括号范围,一旦它超出范围,它将无法访问。这就是为什么当push_back被执行时,它会复制给定的 Monster 对象(通过执行其默认的复制构造函数,或自定义的构造函数,如果有的话),并为向量创建一个新对象。

如果您不想复制要复制的对象,请执行以下操作:

将向量声明为std::vector<std::unique_ptr<Monster>>和 称push_back.push_back(make_unique<Monster>())

在这里,您不是在本地堆栈上声明 Monster,而是在堆上声明它,并使用智能指针来管理堆上对象的生命周期。