对象切片如何导致内存损坏? C++专家和D语言创建者说:

对象切片如何导致内存损坏? C++专家和D语言创建者说:,c++,inheritance,d,memory-corruption,object-slicing,C++,Inheritance,D,Memory Corruption,Object Slicing,切片问题很严重,因为它会导致内存不足 腐败,很难保证一个程序不腐败 忍受它。要设计它,请使用支持 继承只能通过引用访问(不能通过值访问)。这个 D编程语言具有这个特性 P>如果有人给出一个C++例子,对象切片问题会导致内存损坏,那会更好吗?D语言如何解决这个问题? < P>下面的简单的C++程序,其输出显示了切片问题,以及为什么会导致内存损坏。 对于D、Java和C等语言,变量是通过引用句柄访问的。这意味着关于变量的所有信息都与引用句柄相关联。使用C++,当编译完成时,变量的信息是编译器状态的

切片问题很严重,因为它会导致内存不足 腐败,很难保证一个程序不腐败 忍受它。要设计它,请使用支持 继承只能通过引用访问(不能通过值访问)。这个 D编程语言具有这个特性


<> P>如果有人给出一个C++例子,对象切片问题会导致内存损坏,那会更好吗?D语言如何解决这个问题?

< P>下面的简单的C++程序,其输出显示了切片问题,以及为什么会导致内存损坏。 对于D、Java和C等语言,变量是通过引用句柄访问的。这意味着关于变量的所有信息都与引用句柄相关联。使用C++,当编译完成时,变量的信息是编译器状态的一部分。打开C++运行时类型信息(RTI)可以提供一种机制来查看运行时的对象类型,但是它对切片问题没有帮助。 基本上,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;
}

很难很好地建模继承的一个方面是,在某些情况下,可以说:

  • T
    应可分配给
    U
    类型的变量
  • *T
    应可分配给
    *U
  • const*T
    应可分配给
    const*U
  • 但是C++没有区分它们。Java和C#通过只提供第二种语义来避免这个问题(不可能有保存类对象实例的变量;虽然这些语言不使用指针表示法,但所有类类型变量都隐式引用存储在其他地方的对象)。然而,在C++中,没有简单的声明形式,它只允许第二个或第三个形式没有第一个,也没有任何方法可以区分“指向可以存储在变量<代码> u>代码>的变量中的指针”,从“指向包含代码> u>代码>的所有虚拟和非虚拟成员的指针中区分。语言的类型系统可以区分“严格”和“非严格”指针类型,并允许类
    U
    的虚拟方法指定:

  • 它必须被不能存储在
    U
    类型变量中的任何类型重写,并且

  • 在该方法中,
    的类型应为
    U strict*
    ,并且取消引用
    U strict*
    类型的变量应产生
    U strict
    类型的右值,该右值应可分配给
    U
    类型中的一个,即使
    U
    类型的右值不会被分配

  • 然而,C++没有提供这样的区别,这意味着无法区分需要指向可存储在
    U
    类型变量中的对象的指针的方法和需要具有相同成员的对象的方法。

    请考虑

    class Account
    {
        char *name = new char[16];
    
        public: virtual ~Account() { delete[] name; }
        public: virtual void sayHello() { std::cout << "Hello Base\n"; }
    
    };
    
    class BankAccount : public Account
    {
        private: char *bankName = new char[16];
        public: virtual ~BankAccount() override { delete[] bankName; }
        public: virtual void sayHello() override { std::cout << "Hello Derived\n"; }
    
    };
    
    int main()
    {
        BankAccount d;
    
        Account a1 = d; // slicing
        Account& a2 = d; // no slicing
    
        a1.sayHello(); // Hello Base
        a2.sayHello(); // Hello Derived
    
    }
    
    类帐户
    {
    char*name=新字符[16];
    public:virtual~Account(){delete[]name;}
    
    public:virtualvoid sayHello(){std::你为什么不呢?或者读一下评论。@juanchopanza:如果我问另外一个问题会有什么问题?这在将来会很有用,如果将来有人遇到同样的问题,很容易搜索。这是完全错误的。这个问题与链接的问题不同。沃特?读沃尔特·布赖特的答案。它基本上有相同的引语。没有备份的方式不多,但他确实给出了一个例子(用文字,不幸的是不是代码…)无论如何,我重新打开了。我不认为“重复”在将LevelTwo分配给LevelOne之后,LevelTwo仍然存在并被正确地销毁。LevelOne只有共享部分的成员变量,这就是复制的全部内容(否则会损坏堆栈)所以你给出的例子其实很好。Wikipedia有一个Why will
    a1
    leak
    bankName
    ?will
    a1
    实际上包含
    bankName
    ?这个答案不正确,但仍然是一个说明切片不好的例子。这个例子不会泄漏内存,因为
    d
    仍然被破坏并将释放
    bankName
    。注意,
    name
    将被释放两次——一次是在
    a1
    被销毁时,一次是在
    d
    被销毁时。因此,这仍然是由于切片导致内存使用不安全的一个例子,但这不是内存泄漏。@William:你没有错,显然是在6年前,我想表达slici是如何使用的
    class Account
    {
        char *name = new char[16];
    
        public: virtual ~Account() { delete[] name; }
        public: virtual void sayHello() { std::cout << "Hello Base\n"; }
    
    };
    
    class BankAccount : public Account
    {
        private: char *bankName = new char[16];
        public: virtual ~BankAccount() override { delete[] bankName; }
        public: virtual void sayHello() override { std::cout << "Hello Derived\n"; }
    
    };
    
    int main()
    {
        BankAccount d;
    
        Account a1 = d; // slicing
        Account& a2 = d; // no slicing
    
        a1.sayHello(); // Hello Base
        a2.sayHello(); // Hello Derived
    
    }