0

对不起,很长的帖子,但我需要帮助来理解这一点。

我有下面的代码抛出 _crtisvalidheappointer(block)

#include <iostream>
#include <string>
using namespace std;

#define ASSERT_ERROR
/*#define SOLVE_ASSERT*/

class Player
{
    std::string playerName;
public:
    Player(std::string &playerName) :playerName(playerName) { cout << "Player Constructed\n"; }
    void printPlayerName() const
    {
        std::cout << playerName << endl;
    }
#if defined SOLVE_ASSERT
    virtual ~Player() { cout << "Player Destructed\n"; }
#else
    ~Player() { cout << "Player Destructed\n"; }
#endif
};
#if defined ASSERT_ERROR
class Batsman : virtual public Player
#else
class Batsman : public Player
#endif
{
public:
    Batsman(std::string playerName) : Player(playerName) { cout << "Batsman info added\n"; }
    ~Batsman() { cout << "Batsman Destructed\n"; }
};

int main()
{
    Player *ptr = new Batsman("Sachin Tendulkar");
    ptr->printPlayerName();
    delete ptr;
}

只要我在下面取消注释,我就会解决这个问题。

#define SOLVE_ASSERT

我提到了下面的链接,它说单一继承不会导致问题。 https://stackoverflow.com/a/48792493/8679501

但在我的情况下,我只是使用 virtual 来继承类,这有点强制我使析构函数成为虚拟的。为什么会这样?

为什么下面的代码行没有虚拟继承是合法的,为什么没有虚拟继承?

Player *ptr = new Batsman(playerName);
4

1 回答 1

0

在我开始之前,这是我用来调试的代码的略微修改版本:

#include <iostream>
#include <string>
using namespace std;

class Player {
    std::string playerName;
public:
    Player(std::string &playerName) :playerName(playerName) { 
        cout << "Player Constructed @" << this << endl; 
    }
    
    void printPlayerName() const {
        std::cout << playerName << endl;
    }
    
    ~Player() { 
        cout << "Player Destructed @" << this << endl; 
    }

};

class Batsman : virtual public Player {
public:
    Batsman(std::string playerName): 
        Player(playerName)
    { 
        cout << "Batsman info added @" << this << endl; 
    }
    
    ~Batsman() { 
        cout << "Batsman Destructed @" << this << endl; 
    }
};

int main() {
    Player *ptr = new Batsman("Sachin Tendulkar");
    ptr->printPlayerName();
    delete ptr;
}

如果我们尝试在当前状态下运行您的代码,我们会收到以下输出消息(尽管指针值可能会有所不同):

Player Constructed @0x1048c68                                                                                                                                           
Batsman info added @0x1048c60                                                                                                                                           
Sachin Tendulkar                                                                                                                                                        
Player Destructed @0x1048c68                                                                                                                                            
*** Error in `./a.out': free(): invalid pointer: 0x0000000001048c68 ***                                                                                                 
Aborted (core dumped)

如果我们查看指针值,我们会注意到内存在错误的位置被释放。对于另一个示例,请考虑以下代码:

int* arr = new int[100];
cout << "Array is @" << arr << endl << "Memory being freed is @" << (arr+50) << endl;
delete[] (arr+50);

如果我们运行它,我们会得到与您在示例中得到的非常相似的输出:

Array is @0xd8cc20                                                                                                                                                      
Memory being freed is @0xd8cce8                                                                                                                                         
*** Error in `./a.out': free(): invalid pointer: 0x0000000000d8cce8 ***                                                                                                 
Aborted (core dumped)

我们收到此错误是因为 200 字节之间的 200 字节实际上0xd8cc200xd8cce8没有被释放,从而造成内存泄漏。回到你的程序,我们注意到0x1048c60和之间的字节0x1048c68没有被释放。为了解决这个问题,我们必须确保调用了正确的析构函数,我们可以通过以下两种方式之一来做到这一点:

  1. 我们可以声明ptr为 aBatsman*而不是 a Player*。这将告诉编译器调用Batsman析构函数而不是Player 构造函数,从而成功释放所有内存。
  2. 我们可以制作~Player()函数virtual。这向编译器表明将在运行时确定要调用的正确函数。如果这样做了,Batsman将调用析构函数,即使该对象被声明为指向 a 的指针Player

编辑:这是为什么删除似乎可以解决错误的virtual解释class Batsman : virtual public Player

当您使用virtual继承时,子类将在超类之前分配其内存。因此,超类开始分配其内存的时间点将在对象的实际启动之后。这解释了为什么在您的示例中,该Batsman对象在之前分配了 8 个字节Player。当free()尝试使用超类地址解除分配时,它会看到那里没有分配对象,从而导致错误。

同时,在正常的继承中,子类和超类都将在同一个地址开始分配。这意味着您可以使用超类地址释放内存。由于在超类的地址分配了一个指针,C++ 内存管理系统就好像它是一个有效的解除分配一样。

于 2021-03-03T23:58:45.183 回答