Goals
I suppose these are what you are trying to obtain by adopting all the complicated patterns:
- interface, i.e., "multiple types, same set of methods"
- some sort of abstract factory pattern, i.e., you want a "instantiator" who provides a static method (which is not very different from a global function) to call, and returns instances of derived classes
- prohibit users from directly calling the ctors of derived classes
- take care of the dtors by implementing the
Delete()
method
Requirement 1-3
There are at least 3 ways to meet requirement 1-3, as explained below:
1. Derived classes hiding static method of their base
This is the easiest way, and it's fully capable of current main.cpp
. Derived classes can override static methods of their base class.
In file MallardDuck.h
and RedHeadDuck.h
:
// Replace this:
// friend IQuackable* IQuackable::CreateInstance();
// With this:
static IQuackable* CreateInstance();
In file MallardDuck.cpp
(and RedHeadDuck.cpp
similarly):
// Replace this:
// IQuackable* IQuackable::CreateInstance() {
// return static_cast<IQuackable*>(new MallardDuck());
// }
// With this:
IQuackable* MallardDuck::CreateInstance() {
return new MallardDuck();
}
The problem with this is that: other derived classes that don't override and hide CreateInstance()
will still expose IQuackable::CreateInstance()
as a "fallback". Thus:
- if you don't actually implement
IQuackable::CreateInstance()
(so far, you don't have to), then once it is called via a derived class, the code won't compile and won't give a reason that's comprehensible to others; or
- if you choose to implement it, you'd better throw an exception within it, which may surprise your user; otherwise you would have to return a
nullptr
or something, which is the worst practice in C++ (that's what we do in C since it has no language-level support for error handling; any C++ function that cannot fulfill its job should never return).
Either way is not elegant.
2. Adopt abstract factory pattern
This pattern requires a cooperating "factory class", which is abstract; then whenever you derive a concrete quackable, derive also its factory.
In your case you'll need to sketch out a IQuackableFactory
, exposing IQuackableFactory::CreateInstance()
, then derive a MallardDuckFactory
and a RedHeadDuckFactory
.
There are plenty of good examples already, so I won't demonstrate here.
3. Feature injection by using CRTP
There's yet another way of doing things. By CreateInstance()
you're actually providing a "Give me an instance of this class!" feature. Typically we use the CRTP (curiously recurring template pattern) to "inject" a certain feature into a class.
First write this file EnableCreateInstance.hpp
:
#ifndef _ENABLECREATEINSTANCE_HPP_
#define _ENABLECREATEINSTANCE_HPP_
template <class T>
struct EnableCreateInstance {
static T* CreateInstance() { return new T(); }
};
#endif //_ENABLECREATEINSTANCE_HPP_
Then in MallardDuck.h
:
// Add:
#include "EnableCreateInstance.hpp"
class MallardDuck : public IQuackable, public EnableCreateInstance<MallardDuck> {
private:
MallardDuck();
friend class EnableCreateInstance<MallardDuck>;
...
In file RedHeadDuck.h
do the similar: include header, publicly inherit EnableCreateInstance<RedHeadDuck>
, and declare EnableCreateInstance<RedHeadDuck>
as friend class.
This provides more flexibility: you're still providing an interface CreateInstance()
, but in a less "aggressive" way: derived classes have their freedom to choose whether or not to provide CreateInstance()
. If they do, just inherit and (if ctor made private) declare friendship; if not, omit the additional inheritance.
Requirement 4
Well, actually you can use delete this
in non-static non-dtor method. But:
- You must ensure (which can be difficult) that no more access to the deleted object's data members or virtual functions are made, otherwise it causes undefined behaviors;
- You leave your users with dangling pointers to the deleted object.
So, we seldom provide such "deleters" in modern C++. You can get all the benefits it may provide through smart pointers, plus the ability to avoid UBs and so much more.