【问题标题】:C++: Pure virtual destructor in abstract class with membersC ++:具有成员的抽象类中的纯虚拟析构函数
【发布时间】:2014-03-05 03:07:18
【问题描述】:

我刚开始学习 C++ 并偶然发现了这个问题.. 我用纯虚析构函数编写了这个抽象类:

#ifndef ANIMAL
#define ANIMAL
#include <string>
using namespace std;

class Animal {
public:
    Animal();
    virtual ~Animal() = 0;
    Animal(string name, int age);
    virtual string says() = 0;
    void setName(string name);
    void setAge(int age);
    string getName() const;
    int getAge() const;

private:
    int _age;
    string _name;
};
inline Animal::~Animal() { }

像这样动态创建和销毁...

Animal** animalArray = new  Animal*[10];
animalArray[0] = new Dog(name, age);
animalArray[1] = new Cat(name, age);
animalArray[2] = new Owl(name, age);

delete[] animalArray;

我想知道如果 Animal 对象是动态创建然后销毁的,由于 Animal 类的析构函数为空,_age 和 _name 成员会被正确销毁吗?如果有,为什么?

谢谢:D

【问题讨论】:

  • 但是,在您的代码中,并非所有动物都被销毁。你有new-ed 他们但还没有做任何delete
  • 而且,实际上您的问题与虚拟析构函数无关。你只是想知道一个普通的 dtor 是如何工作的
  • 作为刚开始学习 C++ 的人,使用std::vector&lt;std::shared_ptr&lt;Animal&gt;&gt; 可能会更好。并且永远不要在全局范围内的标头中使用use namespace
  • @AdrianShum 我明白了,我认为它“可能”与虚拟析构函数有关的原因是因为我已经声明了 inline Animal::~Animal() { } 并且它是空的让我担心
  • 一个简单的经验法则:不要在标题中使用using/using namespace。始终在标题中使用全名。这将避免命名空间污染。不过在 impl 文件中使用 using/using namespace 很好。

标签: c++ destructor pure-virtual virtual-destructor


【解决方案1】:

在您发布的示例中,您实际上并没有正确销毁所有内容。在行中

delete[] animalArray;

您正在删除Animal*s 的数组。请注意,这不会自动销毁所指向的东西!您必须这样做:

for(int i = 0; i < 3; ++i)
    delete animalArray[i];
delete[] animalArray;

这会破坏每个元素,然后会破坏容器。

现在,您的实际问题是询问私有成员变量是否会被彻底销毁。答案是肯定的——在你的析构函数运行之后,任何静态分配变量的析构函数也将被编译器调用。他们有义务自己清理。当您像示例中那样进行多态性时,确实会调用(空)析构函数Animal::~Animal

请注意,这带有与上面的代码相同的警告:如果你有

string* _name;

您在构造函数中动态分配(使用new),那么string* 将被销毁,但指向 string 不会。因此,在这种情况下,您必须手动调用 delete 才能正确清理。

【讨论】:

  • 酷!这解释了很多。谢谢:D
  • 没问题!关于泄漏性能测试,您可能想要查看的一种工具是Valgrind。如果内存泄漏,它会通知您。
【解决方案2】:

是的,他们会的。通过将 Animal 析构函数设为虚拟(或在您的情况下为纯虚拟,与您的问题无关),您可以确保在使用 Animal 作为基类时正确销毁所有内容。

Animal 的析构函数会以相反的初始化顺序为它的每个成员调用析构函数(即它会先销毁 _name,然后再销毁 _age),从而确保所有内容都被正确释放。

【讨论】:

    【解决方案3】:

    According to Herb Sutter 你不能用纯虚析构函数实例化一个类,除非它也有一个主体。原因是任何派生类都需要在自己的析构函数完成后调用该析构函数。

    我们可以使用至少一个编译器来验证这一点:http://ideone.com/KcwL8W

    #include <string>
    
    class Animal
    {
        public:
        virtual ~Animal() = 0;
    
        std::string _name;
    };
    
    class Dog : public Animal
    {
    };
    
    int main() {
        Animal* pet = new Dog;
        delete pet;
        return 0;
    }
    
    /home/abDVbj/cc8ghrZk.o: In function `Dog::~Dog()':
    prog.cpp:(.text._ZN3DogD2Ev[_ZN3DogD5Ev]+0xb): undefined reference to `Animal::~Animal()'
    /home/abDVbj/cc8ghrZk.o: In function `Dog::~Dog()':
    prog.cpp:(.text._ZN3DogD0Ev[_ZN3DogD0Ev]+0x12): undefined reference to `Animal::~Animal()'
    

    【讨论】:

    • 但是如果定义纯虚函数就可以 Animal::~Animal(){}
    • @michaeltang,是的,我在问题中错过了这一点。谢谢。
    【解决方案4】:

    会的,析构函数并不是真正销毁你创建的对象,它是在对象被销毁之前调用的,如果你在构造函数中没有新的东西,你不需要删除它。

    我试着找出一个样本来证明

    当使用字符串(带有指针成员)对象作为成员变量时,它的析构函数将被调用,即使我们在类的析构函数中什么也不做

    所以我尝试使用用户自定义的String作为对象,这样我们就很容易在析构函数中写一些日志。

    它输出:

    constructor is called
    constructor is called
    constructor is called
    operator constructor is called
    destructor is called
    operator constructor is called
    destructor is called
    virtual ~Dog()
    virtual ~Animal()
    destructor is called
    

    它表明当调用 virtual ~Animal() 时,Animal 类中的字符串对象的析构函数被调用。

    我们可以将字符串对象更改为字符串*(在构造函数中使用new),同时在析构函数中什么也不做,我们会看到字符串的析构函数没有被调用

    #include <iostream>
    #include <string.h>
    
    using namespace std;
    
    
    class String{
    public:
        String(const char *str = NULL);
        String(const String &str);
        ~String();
        String operator+(const String & str);
        String & operator=(const String &str);
        bool operator==(const String &str);
        int Length();
        friend ostream & operator<<(ostream &o,const String &str);
        String SubStr(int start, int end);
    private:
        char * charArray;
    };
    
    String::String(const char *str)
    {
        if(str == NULL){
            charArray=new char[1];
            charArray[0]='\0';
        }else{
            charArray=new char[strlen(str)+1];
            strcpy(charArray,str);
        }
        std::cout<< "constructor is called" << std::endl;
    }
    String::String(const String &str)
    {
        std::cout<< "constructor is called" << std::endl;
        charArray = new char[strlen(str.charArray)+1];
        strcpy(charArray,str.charArray);
    }
    String::~String()
    {
        std::cout<< "destructor is called" << std::endl;
        delete [] charArray;
    }
    String String::operator+(const String &str)
    {
        String res;
        delete [] res.charArray;
        res.charArray = new char[strlen(charArray)+strlen(str.charArray)+1];
        strcpy(res.charArray,charArray);
        strcpy(res.charArray+strlen(charArray),str.charArray);
        return res;
    }
    String & String::operator=(const String &str)
    {
        if(charArray == str.charArray)
            return *this;
        delete [] charArray;
        charArray = new char[strlen(str.charArray)+1];
        strcpy(charArray,str.charArray);
        std::cout<< "operator constructor is called" << std::endl;
        return *this;
    }
    bool String::operator==(const String &str)
    {
        return strcmp(charArray,str.charArray) == 0;
    }
    int String::Length()
    {
        return strlen(charArray);
    }
    ostream & operator<<(ostream &o, const String &str)
    {
        o<<str.charArray;
        return o;
    }
    
    String String::SubStr(int start, int end)
    {
        String res;
        delete [] res.charArray;
        res.charArray = new char[end-start+1];
        for(int i=0; i+start<end; i++){
            res.charArray[i]=charArray[start+i];
        }
        res.charArray[end-start] = '\0';
        return res;
    }
    
    
    class Animal {
    public:
        Animal();
        virtual ~Animal()=0;
        Animal(String name, int age);
    
    public:
        int _age;
        String _name;
    };
    Animal::~Animal(){
        std::cout << "Animal::~Animal()" << std::endl;
    }
    Animal::Animal(String name, int age)
    {
        this->_name = name;
        this->_age = age;
    }
    
    class Dog :public Animal
    {
    public:
        virtual ~Dog() {
             std::cout << "virtual ~Dog()" << std::endl;
        };
        Dog(String name, int age):Animal(name,age)
        {
            this->_name = name;
            this->_age = age;
        }
    };
    
    int main(){
       Animal* p = new Dog( String("dog"),1);
       delete p;
       return 0;
    }
    

    【讨论】:

    • string 对象内部可能有一个指针,因此调用其析构函数很重要。
    • 请注意,在他的示例中没有调用析构函数,因为他没有delete animalArray 的元素。
    • @MarkRansom 字符串对象是类的成员变量,会调用它的析构函数。
    猜你喜欢
    • 2019-04-20
    • 2011-03-21
    • 2019-12-08
    • 2023-03-23
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 2013-01-15
    • 2016-03-01
    相关资源
    最近更新 更多