简体   繁体   中英

C++: Pure virtual destructor in abstract class with members

I've just started learning C++ and stumbled across this problem.. I've written this abstract class with pure virtual destructor:

#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() { }

Created dynamically and destroyed like this...

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;

and I am left wondering if an Animal object is created dynamically and then destroyed, will the _age and _name members get destroyed properly since the destructor for Animal class is empty? If so, why?

Thanks :D

In the example you posted, you're actually not destroying everything correctly. In the line

delete[] animalArray;

you are deleting an array of Animal* s. Note that this does not automatically destroy the things being pointed to! You would have to do:

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

This destroys each element, then destroys the container.

Now, your actual question is asking whether the private member variables are going to be cleanly destroyed. The answer is yes--after your destructor runs, any statically allocated variables' destructors will also be called by the compiler. It is their obligation to clean up after themselves. When you're doing polymorphism as in your example, the (empty) destructor Animal::~Animal will indeed be called.

Note that this carries the same warning as the code above: if you instead have

string* _name;

that you dynamically allocate in the constructor (with new ), then the string* will be destroyed, but the pointed to string will not be. So in that case, you would have to manually call delete to properly clean up.

it will , destructor is not really destroy the object you created , it is called before the object being destroied, if you have not new something in constructor , there is no need for you to delete it.

I try to finger out a sample to prove

when using string(with a pointer member) object as member variable, its destructor will be called, even we do nothing in the class's destructor

so I tried to used a user-defined String as object, so it is easy for us to write some log in the destructor.

it outputs:

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

it show is when virtual ~Animal() is called , the string object'destructor in the Animal class is called.

we can change the string object to string*(using new in construtor) while still doing nothing in the destructor , and we will see the string's destructor is not called

#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;
}

yes they will. By making the Animal destructor virtual (or pure virtual in your case, doesn't matter regarding your question) you make sure that everything is properly destroyed when using Animal as a base class.

The destructor of Animal will call the destructor for each of it's members in reverse initialization order (ie it will destroy _name first and _age afterwards) and thereby makes sure that everything is properly freed.

According to Herb Sutter you cannot instantiate a class with a pure virtual destructor unless it also has a body. The reason is that any derived class will need to call that destructor after its own destructor is finished.

We can verify this with at least one compiler: 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()'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM