I searched for this, but I didn't really understand the answers.
I am completely new to C++, and what I am trying to achieve is to have an abstract class which serves as a base class for my object types so that I can store my objects in an array of pointers of type of the abstract class instead of using void *
. Moreover, my objects share a few common member functions which could easily reduce my code base with the abstract class implementation.
However, I am confused about the constructor and destructor for the abstract class.
The abstract class doesn't really need a constructor since the parameters that could be passed in that are common to both require different things to be done with said parameter in the derived class to set the protected attributes correctly (sizing of a matrix). So, is it okay to not have a constructor? Also, since I don't have a constructor what should the destructor be?
I say an answer where it was to implement a virtual destructor. However, I'm not sure what this means and there was a discussion about potential memory leaks saying there wouldn't be any as long as the derived classes reimplemented the destructor. So, this does mean that I can implement a virtual decstructor and then in the derived objects say Foo
and Bar
I simply implement ~Foo
and ~Bar
to prevent memory leaks (assuming they're correct of course)? I wasn't confident I understood what the re-implementation in the derived classes meant exactly.
If the constructor for an abstract class calls a pure virtual function, either directly or indirectly, the result is undefined. However, constructors and destructors for abstract classes can call other member functions.
You can create an abstract base class with only a virtual destructor.
It is used to initialize an object. Yes, an Abstract class always has a constructor.
Yes, the destructor is nothing more than a function. You can call it at any time. However, calling it without a matching constructor is a bad idea.
In general, when implementing abstract base classes, you have two recommended options for destructors (source):
Use this when you intend to have pointers of your base class, which may point to instances of the derived class. For example:
class MyBase {
public:
virtual ~MyBase() {};
};
class MyDerived : public MyBase {
public:
virtual ~MyDerived() {};
}
std::unique_ptr<MyBase> pInstance = std::make_unique<MyDerived>();
By making the destructor virtual in the base class (and also in the derived class), you are ensuring that the destructor for MyDerived
gets called at runtime. If the destructor is non-virtual, calling delete
on a pointer to MyBase
will NOT call the destructor of MyDerived
.
Use this in cases where you do not want to allow the user to create base-class pointers to your derived object.
class MyBase {
protected:
~MyBase() {};
};
class MyDerived : public MyBase {
public:
~MyDerived() {};
}
// NOT ALLOWED: std::unique_ptr tries to call protected destructor.
std::unique_ptr<MyBase> pBadInstance = std::make_unique<MyDerived>();
// Allowed: std::unique_ptr calls public MyDerived::~MyDerived()
std::unique_ptr<MyDerived> pGoodInstance = std::make_unique<MyDerived>();
This comes with an important caveat, however. If you have a deep inheritance hierarchy, having a non-virtual destructor means that you must enforce this rule all the way up your heirarchy. For example:
class MyBase {
protected:
~MyBase() {};
};
class MyDerived : public MyBase {
public:
~MyDerived() {};
}
class MyDerivedAgain : public MyDerived {
public:
~MyDerivedAgain() {};
}
// Uh oh! MyDerivedAgain destructor would not be called!
std::unique_ptr<MyDerived> pGoodInstance = std::make_unique<MyDerivedAgain>();
If you choose to go this route, you should ensure that you do not allow any of your base classes to be instantiated. All destructors except for leaf derived classes should be protected.
This seems a little convoluted, but it can have advantages such as avoiding vtable space, and mildly improving performance in tight loops by dodging vtable lookups at runtime (a micro-optimization at best).
It is perfectly okay to omit the constructor in any class as long as all variables can be default-constructed (or do not have a constructor (e.g. int)). The C++ compiler will simply create the default constructor MyAbstractClass::MyAbstractClass() { }
. That said, it is usually preferable to create a constructor to initialize any abstract class variables:
Okay:
class MyBase {
protected:
int _x;
int _y;
};
class MyDerived : public MyBase {
public:
MyDerived(int x, int y) {
_x = x;
_y = y;
}
};
Better:
class MyBase {
public:
MyBase(int x, int y) : _x(x), _y(y) {
}
protected:
int _x;
int _y;
};
class MyDerived : public MyBase {
public:
MyDerived(int x, int y) : MyBase(x, y) {
}
};
The "Better" version with the MyBase::MyBase(int, int)
constructor is better because it forces _x
and _y
to be initialized immediately, and the compiler will check that the base class constructor is called. By initializing base class variables in the derived constructor, you're potentially inviting disaster because you may forget to initialize a variable and cause all sorts of runtime problems.
If you are implementing an interface class that defines a "contract", you can skip the constructor (interfaces don't have variables or implementation, constructor is not required), and use a public virtual destructor. This ensures that any class implementing the interface is going to get cleaned up when it is deleted.
class MyInterface {
public:
virtual ~MyInterface() = 0;
virtual void MyMethod() = 0;
virtual void MyOtherMethod() = 0;
};
// Base class virtual destructors should always have an implementation,
// even when they are pure-virtual.
MyInterface::~MyInterface() { }
// -----------------------------------------------------------------------------
class MyImplementation : public MyInterface {
virtual ~MyImplementation () { }
virtual void MyMethod() { std::cout << "MyMethod()" << std::endl; }
virtual void MyOtherMethod() { std::cout << "MyOtherMethod()" << std::endl; }
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With