访问冲突 - 为啥基类析构函数被调用两次? [关闭]

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 添加析构函数似乎无法解决问题。

作为参考,我已经包含了PlayerCharacter的实现和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 对象,而这个一个不会初始化PlayerCharacter 部分中的任何内容,即在Character 析构函数中删除的m_abilityAttributesm_primaryAttributes 等字段。

现在,您的Player 中似乎也有一个Character。您可能希望您的Player 构造函数采用Character::Builder 引用而不是Character 指针,该指针将用于初始化/构建您的'CharacterDecorator(and you may have to have aCharacterDecoratorconstructor taking aCharacter 的基础Character: :Builderobject to initialize itsCharacter`base)。

(注意:让 m_player 成员指向 Character 对我来说是一个危险信号)

【讨论】:

我在下面找到了第一段的第二句,但我可能应该使用您在第二段中的建议重构代码。谢谢。【参考方案2】:

虽然我提供了一个不带参数的构造函数,但我没有给它一个指针类型成员的初始化列表:

Character::Character() 

应该是:

Character::Character() 
    : m_abilityAttributes(0), m_primaryAttributes(0)

我猜这是因为基类Character的构造函数在我实例化Player的对象时被调用,但是基类的默认构造函数之前没有成员初始化列表,因此访问调用基本析构函数时的违规错误(?)

【讨论】:

完全正确。当您的 Player 被构造时,它还将 Character 构造为基类。如果不初始化构造函数中的成员,这些成员将是随机值。当 Player 被销毁时,将调用 Character 析构函数,这会删除那些成员,除非它们是随机值,从而导致您的访问冲突。 C++ 有时可能是一个如此苛刻的情妇。我偶尔会忘记一些小(但非常重要)的细节。

以上是关于访问冲突 - 为啥基类析构函数被调用两次? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 继承中,当指向基类的指针对象指向派生类时,不调用派生类析构函数

条款7:为多态基类析构函数声明为virtual

析构函数小结

C++ 构造函数不能是虚函数,基类析构函数应该为虚函数

析构函数 声明为protected

基类析构函数为虚函数