在以下情况下手动调用析构函数是否是一个糟糕的设计决策?
Is it a bad design decision to manually call the destructor in the following case?
所以我目前正在研究我的 OOP/C++ 技能并偶然发现了以下案例:
我正在制作一个由实体组成的游戏,实体必须进食才能生存。他们可以吃其他实体或食物。为了实现这种行为,我创建了一个 edible
接口,它强制每个实现 class 创建一个 getNutritionalInformation()
方法来计算一个单元在盛宴后的饱和度。
所以整个事情应该像这样在代码方面工作:
std::unique_ptr<Entity> e1(new Entity);
std::unique_ptr<Entity> e2(new Entity);
std::cout << "Pre feasting energy: " << e1->getNutritionalInformation() << std::endl;
e1->eat(*e2);
std::cout << "After feasting energy: " << e1->getNutritionalInformation() << std::endl;
此操作后c1
的能量应该比之前更高,这个值目前是在创建实体时随机分配的。但是为了模拟被吃掉的实体的死亡,我想在它被吃掉的时候手动杀死它。我通过以下方式实现了这一目标:
void Entity::eat(Edible& subject) {
this->energy += subject.getNutritionalInformation();
delete &subject;
}
但最后这对我来说似乎有点脏,我真的很想让智能指针以某种方式知道,它当前 holds/points 指向的对象不再有效。有没有更清洁的方法来实现这一目标?我很确定我尝试这样做的方式非常 hacky 并且不被认为是正确的 OOP 编程。
提前感谢您的帮助。
是的,请不要手动执行。
这是您实际上 不需要 关心的少数事情之一。您的程序会为您做到这一点!
规则是:一旦变量离开作用域,它就会被丢弃(调用解构函数)。除了动态分配的内存,您还需要释放(删除)variable/s.
此外,如果您手动删除变量,您的程序会崩溃:
{
Type variable;
delete variable;
} //Scope end: deconstructor get's called. ERROR: already deleated, undefined behaviour, crash. everything destroyed.
好吧,通过 e2
"eating" e1
它实际上取得了它的所有权。具体来说,它必须拥有它,才能摧毁它。所以实际做法是这样的:
void Entity::eat(std::unique_ptr<Edible> subject) {
this->energy += subject->getNutritionalInformation();
}
然后...就是这样。主题将在范围结束时自动销毁。为了使用它,您需要显式调用 std::move
;这表明您正在将所有权从调用范围转移 到 e2。
std::unique_ptr<Entity> e1(new Entity);
std::unique_ptr<Entity> e2(new Entity);
std::cout << "Pre feasting energy: " << e1->getNutritionalInformation() << std::endl;
e1->eat(std::move(e2));
std::cout << "After feasting energy: " << e1->getNutritionalInformation( << std::endl;
请注意,在 e2
上调用 std::move
后,不能再假设 e2
实际上指向一个实体(因为所有权已转移)。
您希望 e2
的所有者对其变量调用 delete
以防止出现问题。
您可以通过两种方式完成此操作:
e1->eat(std::move(e2));
void Entity::eat(std::unique_ptr<Edible> subject) {
this->energy += subject->getNutritionalInformation();
}
或者:
e1->eat(*e2);
e2.reset(nullptr);
在任何一种情况下,您的调用者(第一个示例中的函数)都知道 e2
正在被操作破坏,在第一个实例中我给出的是因为您移出了它,在第二个实例中因为它手动调用了 reset
(这会破坏旧指针)。
另一种方法是让一些更高级别的管理器控制实体的生命周期,因此 eat 期间的 e2 只会执行:e1->kill(); e1->eatenEnergy(能量); (降低它的营养价值,如果部分被吃掉的实体是可能的)。
然后等到实体管理器轮到它,循环遍历所有实体(或对报告的信号做出反应,例如来自 e1->kill() 实现内部的死亡事件)并释放那些完全死亡的实体 eaten/decayed/etc(杀死那些饥饿的人,...)。
如果您想像当前示例一样保留在基于事件的简单世界中,或者您希望世界中发生一些 "life cycles",而 [memory/instance] 管理,则在很大程度上取决于集中在单个 class.
我认为集中式管理器会使事情变得有点复杂,但是一些可能性应该更容易,比如更好地调试检查 initial/end 回合状态,优先处理一些实体,重用实体的内存( w/o 动态分配)等..
在这两种情况下,您都应该提前考虑一些极端情况(主要是竞争条件)将如何解决,例如:
- e1 同时想吃 e2,因为 e2 想吃 e1
- e1和e3同时想吃掉e2
- e3 在 e2 已经被 e1 吃掉后试图吃掉它(e2 是 "dead" 但已分配)
所以实体的状态应该设计成在这种情况下有效,并以期望的结果结束。
当 std::unique_ptr
超出范围时,将自动调用对象的析构函数。你不用担心。
手动销毁对象可能会导致问题。在这种情况下,当eat()
手动销毁subject
对象时,拥有该对象的std::unique_ptr
不知道该对象已被销毁,并且在尝试再次销毁该对象时会崩溃第二次。
所以我目前正在研究我的 OOP/C++ 技能并偶然发现了以下案例:
我正在制作一个由实体组成的游戏,实体必须进食才能生存。他们可以吃其他实体或食物。为了实现这种行为,我创建了一个 edible
接口,它强制每个实现 class 创建一个 getNutritionalInformation()
方法来计算一个单元在盛宴后的饱和度。
所以整个事情应该像这样在代码方面工作:
std::unique_ptr<Entity> e1(new Entity);
std::unique_ptr<Entity> e2(new Entity);
std::cout << "Pre feasting energy: " << e1->getNutritionalInformation() << std::endl;
e1->eat(*e2);
std::cout << "After feasting energy: " << e1->getNutritionalInformation() << std::endl;
此操作后c1
的能量应该比之前更高,这个值目前是在创建实体时随机分配的。但是为了模拟被吃掉的实体的死亡,我想在它被吃掉的时候手动杀死它。我通过以下方式实现了这一目标:
void Entity::eat(Edible& subject) {
this->energy += subject.getNutritionalInformation();
delete &subject;
}
但最后这对我来说似乎有点脏,我真的很想让智能指针以某种方式知道,它当前 holds/points 指向的对象不再有效。有没有更清洁的方法来实现这一目标?我很确定我尝试这样做的方式非常 hacky 并且不被认为是正确的 OOP 编程。
提前感谢您的帮助。
是的,请不要手动执行。 这是您实际上 不需要 关心的少数事情之一。您的程序会为您做到这一点! 规则是:一旦变量离开作用域,它就会被丢弃(调用解构函数)。除了动态分配的内存,您还需要释放(删除)variable/s.
此外,如果您手动删除变量,您的程序会崩溃:
{
Type variable;
delete variable;
} //Scope end: deconstructor get's called. ERROR: already deleated, undefined behaviour, crash. everything destroyed.
好吧,通过 e2
"eating" e1
它实际上取得了它的所有权。具体来说,它必须拥有它,才能摧毁它。所以实际做法是这样的:
void Entity::eat(std::unique_ptr<Edible> subject) {
this->energy += subject->getNutritionalInformation();
}
然后...就是这样。主题将在范围结束时自动销毁。为了使用它,您需要显式调用 std::move
;这表明您正在将所有权从调用范围转移 到 e2。
std::unique_ptr<Entity> e1(new Entity);
std::unique_ptr<Entity> e2(new Entity);
std::cout << "Pre feasting energy: " << e1->getNutritionalInformation() << std::endl;
e1->eat(std::move(e2));
std::cout << "After feasting energy: " << e1->getNutritionalInformation( << std::endl;
请注意,在 e2
上调用 std::move
后,不能再假设 e2
实际上指向一个实体(因为所有权已转移)。
您希望 e2
的所有者对其变量调用 delete
以防止出现问题。
您可以通过两种方式完成此操作:
e1->eat(std::move(e2));
void Entity::eat(std::unique_ptr<Edible> subject) {
this->energy += subject->getNutritionalInformation();
}
或者:
e1->eat(*e2);
e2.reset(nullptr);
在任何一种情况下,您的调用者(第一个示例中的函数)都知道 e2
正在被操作破坏,在第一个实例中我给出的是因为您移出了它,在第二个实例中因为它手动调用了 reset
(这会破坏旧指针)。
另一种方法是让一些更高级别的管理器控制实体的生命周期,因此 eat 期间的 e2 只会执行:e1->kill(); e1->eatenEnergy(能量); (降低它的营养价值,如果部分被吃掉的实体是可能的)。
然后等到实体管理器轮到它,循环遍历所有实体(或对报告的信号做出反应,例如来自 e1->kill() 实现内部的死亡事件)并释放那些完全死亡的实体 eaten/decayed/etc(杀死那些饥饿的人,...)。
如果您想像当前示例一样保留在基于事件的简单世界中,或者您希望世界中发生一些 "life cycles",而 [memory/instance] 管理,则在很大程度上取决于集中在单个 class.
我认为集中式管理器会使事情变得有点复杂,但是一些可能性应该更容易,比如更好地调试检查 initial/end 回合状态,优先处理一些实体,重用实体的内存( w/o 动态分配)等..
在这两种情况下,您都应该提前考虑一些极端情况(主要是竞争条件)将如何解决,例如:
- e1 同时想吃 e2,因为 e2 想吃 e1
- e1和e3同时想吃掉e2
- e3 在 e2 已经被 e1 吃掉后试图吃掉它(e2 是 "dead" 但已分配)
所以实体的状态应该设计成在这种情况下有效,并以期望的结果结束。
当 std::unique_ptr
超出范围时,将自动调用对象的析构函数。你不用担心。
手动销毁对象可能会导致问题。在这种情况下,当eat()
手动销毁subject
对象时,拥有该对象的std::unique_ptr
不知道该对象已被销毁,并且在尝试再次销毁该对象时会崩溃第二次。