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 динамически, а затем уничтожается, будут ли члены _age и _name уничтожены должным образом, поскольку деструктор для класса Animal пуст? Если да, то почему?

Спасибо


person CookiePaw    schedule 05.03.2014    source источник
comment
Однако в вашем коде не все животные уничтожаются. Вы new отредактировали их, но не сделали ни одного delete   -  person Adrian Shum    schedule 05.03.2014
comment
и на самом деле ваш вопрос не имеет ничего общего с виртуальным деструктором. Вам просто интересно как работает обычный дтор   -  person Adrian Shum    schedule 05.03.2014
comment
Как человеку, который только начал изучать C++, вам, вероятно, будет намного лучше с std::vector<std::shared_ptr<Animal>>. И никогда use namespace в заголовке в глобальной области видимости.   -  person n. 1.8e9-where's-my-share m.    schedule 05.03.2014
comment
@AdrianShum Понятно, причина, по которой я думаю, что это может быть как-то связано с виртуальным деструктором, заключается в том, что я объявил inline Animal::~Animal() { }, и меня это беспокоит.   -  person CookiePaw    schedule 05.03.2014
comment
@н.м. Спасибо за совет! На самом деле не знал, как создать массив абстрактных типов, и это первое решение, которое я нашел в сети... поэтому я использовал его.. :( и так... если не использовать use namespace.. std::string для каждой строки, все будет хорошо ??   -  person CookiePaw    schedule 05.03.2014
comment
Да, я вообще предпочитаю std::string везде, но using std::string тоже вариант.   -  person n. 1.8e9-where's-my-share m.    schedule 05.03.2014
comment
Простое практическое правило: не делайте using/using namespace в заголовках. Всегда используйте полное имя в заголовке. Это позволит избежать загрязнения пространства имен. Однако можно использовать using/using namespace в файле impl.   -  person Adrian Shum    schedule 06.03.2014


Ответы (4)


В приведенном вами примере вы на самом деле не все правильно уничтожаете. В соответствии

delete[] animalArray;

вы удаляете массив Animal*s. Обратите внимание, что это не уничтожает автоматически объекты, на которые указывает указатель! Вам нужно будет сделать:

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

Это уничтожает каждый элемент, затем уничтожает контейнер.

Теперь ваш фактический вопрос заключается в том, будут ли закрытые переменные-члены полностью уничтожены. Ответ положительный: после запуска вашего деструктора компилятор также вызовет деструкторы любых статически размещенных переменных. Это их обязанность убирать за собой. Когда вы выполняете полиморфизм, как в вашем примере, (пустой) деструктор Animal::~Animal действительно будет вызываться.

Обратите внимание, что это содержит то же предупреждение, что и приведенный выше код: если вместо этого у вас есть

string* _name;

который вы динамически выделяете в конструкторе (с new), то string* будет уничтожен, но указанный string не будет. Так что в этом случае вам придется вручную вызывать delete для правильной очистки.

person George Hilliard    schedule 05.03.2014
comment
Прохладно! это многое объясняет. Спасибо :D - person CookiePaw; 05.03.2014
comment
Конечно! Одним из инструментов, который вы можете использовать для тестирования производительности утечек, является Valgrind. Это сообщит вам, если у вас есть утечка памяти. - person George Hilliard; 05.03.2014

Да, они будут. Делая деструктор Animal виртуальным (или чисто виртуальным в вашем случае, не имеет значения для вашего вопроса), вы гарантируете, что все будет правильно уничтожено при использовании Animal в качестве базового класса.

Деструктор Animal вызовет деструктор для каждого из своих членов в обратном порядке инициализации (т. е. сначала уничтожит _name, а потом _age) и тем самым удостоверится, что все правильно освобождено.

person Andreas A.    schedule 05.03.2014

Согласно Хербу Саттеру, вы не можете создать экземпляр класса с помощью чисто виртуального деструктора, если он также не имеет тело. Причина в том, что любой производный класс должен будет вызвать этот деструктор после того, как его собственный деструктор завершится.

Мы можем проверить это хотя бы с одним компилятором: 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()'
person Mark Ransom    schedule 05.03.2014
comment
но если вы определите чисто виртуальную функцию, все будет в порядке Animal::~Animal(){} - person michaeltang; 05.03.2014
comment
@michaeltang, ах, да, я пропустил это в вопросе. Спасибо. - person Mark Ransom; 05.03.2014

это будет, деструктор на самом деле не уничтожает созданный вами объект, он вызывается перед уничтожением объекта, если у вас нет чего-то нового в конструкторе, вам не нужно его удалять.

Я пытаюсь найти образец, чтобы доказать

при использовании строкового (с членом-указателем) объекта в качестве переменной-члена будет вызываться его деструктор, даже если мы ничего не делаем в деструкторе класса

поэтому я попытался использовать определяемую пользователем строку в качестве объекта, поэтому нам легко написать журнал в деструкторе.

он выводит:

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

это показывает, что когда вызывается виртуальный ~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;
}
person michaeltang    schedule 05.03.2014
comment
Объект string, вероятно, имеет внутри указатель, поэтому важно, чтобы был вызван его деструктор. - person Mark Ransom; 05.03.2014
comment
Обратите внимание, что в его примере деструкторы не вызываются, поскольку он не выполняет delete элементы animalArray. - person user3286380; 05.03.2014
comment
Строковый объект @MarkRansom является переменной-членом класса, будет вызван его деструктор. - person michaeltang; 05.03.2014