对象切片如何导致内存损坏?

Des*_*tor 5 c++ inheritance d memory-corruption object-slicing

C++ 专家兼 D 语言创始人Walter Bright表示:

切片问题很严重,因为它可能导致内存损坏,并且很难保证程序不会受到这种情况的影响。为了从语言中设计它,支持继承的类应该只能通过引用(而不是通过值)访问。D 编程语言具有这个特性。

如果有人通过给出一个 C++ 示例来解释它会更好,其中对象切片问题导致内存损坏?而这个问题用D语言是如何解决的呢?

Ric*_*ers 2

以下简单的 C++ 小程序及其输出显示了切片问题以及它为何会导致内存损坏。

对于 D、Java 和 C# 等语言,变量是通过引用句柄访问的。这意味着有关变量的所有信息都与引用句柄相关联。对于 C++,有关变量的信息是编译完成时编译器状态的一部分。打开 C++ 运行时类型信息 (RTTI) 可以提供一种在运行时查看对象类型的机制,但它并不能真正帮助解决切片问题。

基本上,C++ 删除了安全网以提高速度。

C++ 编译器有一组使用的规则,因此如果类中未提供特定方法(例如复制构造函数或赋值运算符),编译器将尽力创建自己的默认版本。编译器还具有它使用的规则,因此如果特定方法不可用,那么它将寻找另一种方法来创建表达源语句含义的代码。

有时编译器太有帮助,结果会变得危险。

在这个例子中,有两个类,一个levelOne是基类,一个levelTwo是派生类。它使用虚拟析构函数,以便指向基类对象的指针也将清除该对象的派生类部分。

在输出中,我们看到派生类分配给基类会导致切片,并且当调用析构函数时,仅调用基类的析构函数,而不调用派生类的析构函数。

派生类的析构函数没有被调用的结果意味着派生对象拥有的任何资源可能无法正确释放。

这是简单的程序。

#include "stdafx.h"
#include <iostream>

class levelOne
{
public:
    levelOne(int i = 1) : iLevel(i) { iMyId = iId++; std::cout << "  levelOne construct  " << iMyId << std::endl; }
    virtual ~levelOne() { std::cout << "  levelOne destruct  " << iMyId << "  iLevel = " << iLevel << std::endl; }

    int  iLevel;
    int  iMyId;

    static int iId;
};

int levelOne::iId = 1;

class levelTwo : public levelOne
{
public:
    levelTwo(int i = 2) : levelOne(i) { jLevel = 2; iMyTwoId = iTwoId++;  std::cout << "     levelTwo construct  " << iMyId << ", " << iMyTwoId << std::endl; }
    virtual ~levelTwo() { std::cout << "     levelTwo destruct  " << iMyId << ", " << iMyTwoId << "  iLevel = " << iLevel << "  jLevel = " << jLevel << std::endl; }

    int  jLevel;
    int  iMyTwoId;

    static int iTwoId;
};

int levelTwo::iTwoId = 101;


int _tmain(int argc, _TCHAR* argv[])
{
    levelOne one;
    levelTwo two;

    std::cout << "Create LevelOne and assign to it a LevelTwo" << std::endl;
    levelOne aa;     // create a levelOne object
    aa = two;        // assign to the levelOne object a levelTwo object

    std::cout << "Create LevelTwo and assign to it a LevelOne pointer then delete it" << std::endl;
    levelOne *pOne = new levelTwo;
    delete pOne;

    std::cout << "Exit program." << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

pOne = new levelTwo;输出显示,使用id创建的对象4命中了levelTwo,并且levelOne析构函数正确处理了对象销毁。

levelTwo然而,将对象分配twolevelOne对象aa会导致切片,因为使用了默认的赋值运算符(仅执行内存复制),因此当调用对象的析构函数时,仅执行aa析构函数,这意味着该对象拥有的任何资源levelOne派生类不会被释放。

然后,其他两个对象被正确销毁,因为它们在程序结束时都超出了范围。阅读此日志请记住,析构函数是按与构造相反的顺序调用的。

  levelOne construct  1
  levelOne construct  2
     levelTwo construct  2, 101
Create LevelOne and assign to it a LevelTwo
  levelOne construct  3
Create LevelTwo and assign to it a LevelOne pointer then delete it
  levelOne construct  4
     levelTwo construct  4, 102
     levelTwo destruct  4, 102  iLevel = 2  jLevel = 2
  levelOne destruct  4  iLevel = 2
Exit program.
  levelOne destruct  2  iLevel = 2
     levelTwo destruct  2, 101  iLevel = 2  jLevel = 2
  levelOne destruct  2  iLevel = 2
  levelOne destruct  1  iLevel = 1
Run Code Online (Sandbox Code Playgroud)