访问冲突 - 为啥基类析构函数被调用两次? [关闭]
Posted
技术标签:
【中文标题】访问冲突 - 为啥基类析构函数被调用两次? [关闭]【英文标题】:Access violation - Why is base class destructor getting called twice? [closed]访问冲突 - 为什么基类析构函数被调用两次? [关闭] 【发布时间】:2012-12-18 06:25:25 【问题描述】:我有一个类Player
,它试图实现Decorator
模式。 Player
包含其基类Character
的成员,称为m_player
。从客户端调用Player
的析构函数时,我遇到了一些导致内存访问冲突的问题。从main
开始:
Character* createBaseClass();
// more forward declarations
int main (int argc, char* const argv[])
Player* mainCharacter = new Player(createBaseCharacter());
delete mainCharacter; // Crashes when calling delete
return 0;
Character* createBaseCharacter()
return Character::Builder()
.name("Dylan")
.description("Super bad-ass hero of the game")
.build();
我在mainCharacter
上调用delete
运算符后不久发生错误,调用顺序如下:
Player::~Player()
delete m_armor;
delete m_weapon;
delete m_player; // calls Character's destructor
然后是Character
的析构函数:
Character::~Character()
// works fine
//
delete m_abilityAttributes;
m_abilityAttributes = NULL;
delete m_primaryAttributes;
m_primaryAttributes = NULL;
然而,奇怪的是,这个析构函数似乎被调用了两次——一旦完成上述操作,调试器就会带我去反汇编,逐步通过“标量删除析构函数”,它似乎再次调用Character
析构函数,通过Player
的接口,称为CharacterDecorator
:
崩溃点的调用堆栈:
调用CharacterDecorator
的析构函数会导致随后调用Character
的析构函数:
Character::~Character()
// Crashes with Access Violation
//
delete m_abilityAttributes;
m_abilityAttributes = NULL;
delete m_primaryAttributes;
m_primaryAttributes = NULL;
此时我完全糊涂了——我不知道为什么析构函数会通过抽象接口CharacterDecorator
再次被调用,而析构函数会通过其具体实现被调用。此外,向CharacterDecorator
添加析构函数似乎无法解决问题。
作为参考,我已经包含了Player
、Character
的实现和CharacterDecorator
的接口:
class CharacterDecorator : public Character
public:
virtual Armor* getArmor() const = 0;
virtual Weapon* getWeapon() const = 0;
;
Player
:
Player::Player()
Player::Player(Character* player)
:m_player(player)
,m_weapon(0)
,m_armor(0)
Player::Player(Character* player, Weapon* weapon, Armor* armor)
:m_player(player)
,m_weapon(weapon)
,m_armor(armor)
Player::~Player()
delete m_armor;
delete m_weapon;
// getters
Armor* Player::getArmor() const
return m_armor;
Weapon* Player::getWeapon() const
return m_weapon;
// additional methods ...
Character
:
Character::Character()
Character::Character(const Builder& builder)
:m_name(builder._name)
,m_description(builder._description)
,m_abilityAttributes(builder._abilityAttributes)
,m_primaryAttributes(builder._primaryAttributes)
Character::Character(const Character& rhs)
m_name = rhs.m_name;
m_description = rhs.m_description;
m_abilityAttributes = new AbilityAttributes();
m_primaryAttributes = new PrimaryAttributes();
*m_abilityAttributes = *rhs.m_abilityAttributes;
*m_primaryAttributes = *rhs.m_primaryAttributes;
Character::~Character()
delete m_abilityAttributes;
m_abilityAttributes = NULL;
delete m_primaryAttributes;
m_primaryAttributes = NULL;
// additional methods ...
// Builder pattern methods
//
Character::Builder::Builder()
: _abilityAttributes(0), _primaryAttributes(0)
Character* Character::Builder::build()
return new Character(*this);
Character::Builder& Character::Builder::abilityAttributes(AbilityAttributes* value)
_abilityAttributes = value;
return *this;
Character::Builder& Character::Builder::primaryAttributes(PrimaryAttributes* value)
_primaryAttributes = value;
return *this;
【问题讨论】:
为什么要复制创建一个新角色new Character(player)
而不是使用工厂创建的那个?
您能否将代码简化到重现问题所需的绝对最低限度,并完整发布 -- 可运行 -- 结果?
是否可以尝试依赖智能指针的方法?我建议您使用 unique_ptr 或 scoped_ptr,然后尝试找出何时需要 shared_ptr。也许您将能够追踪问题的根源。
@neagoegab:那只会隐藏问题。任何代码路径都不应该多次意外删除内容。
@StarPilot 在 C++ 中删除零是安全的,不需要守卫。
【参考方案1】:
您的Player
类继承自CharacterDecorator
,后者继承自Character
。因此,当您在 Player
对象上调用析构函数时,您将删除 m_player 对象(调用其 Character
析构函数),然后再次在 Player
对象的基础 Character
部分上删除。除了我不是你想做的事情之外,你还有一个问题,当你创建 Player
时,默认的 Character
构造函数被调用来创建基础 Character
对象,而这个一个不会初始化Player
的Character
部分中的任何内容,即在Character
析构函数中删除的m_abilityAttributes
和m_primaryAttributes
等字段。
现在,您的Player
中似乎也有一个Character
。您可能希望您的Player
构造函数采用Character::Builder
引用而不是Character
指针,该指针将用于初始化/构建您的'CharacterDecorator(and you may have to have a
CharacterDecoratorconstructor taking a
Character 的基础Character
: :Builderobject to initialize its
Character`base)。
(注意:让 m_player 成员指向 Character
对我来说是一个危险信号)
【讨论】:
我在下面找到了第一段的第二句,但我可能应该使用您在第二段中的建议重构代码。谢谢。【参考方案2】:虽然我提供了一个不带参数的构造函数,但我没有给它一个指针类型成员的初始化列表:
Character::Character()
应该是:
Character::Character()
: m_abilityAttributes(0), m_primaryAttributes(0)
我猜这是因为基类Character
的构造函数在我实例化Player
的对象时被调用,但是基类的默认构造函数之前没有成员初始化列表,因此访问调用基本析构函数时的违规错误(?)
【讨论】:
完全正确。当您的 Player 被构造时,它还将 Character 构造为基类。如果不初始化构造函数中的成员,这些成员将是随机值。当 Player 被销毁时,将调用 Character 析构函数,这会删除那些成员,除非它们是随机值,从而导致您的访问冲突。 C++ 有时可能是一个如此苛刻的情妇。我偶尔会忘记一些小(但非常重要)的细节。以上是关于访问冲突 - 为啥基类析构函数被调用两次? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章