Reputation: 912
I'm implementing a virtual clone()
method that can be entirely identical with the exception of the "actual" type of the object returned, and I realized that there's going to be a lot of duplication. For example, here's the obvious solution that I want to avoid:
AbstractVehicle* ConcreteCar::clone() const
{
auto c = new ConcreteCar;
c->setSomething(this->getSomething());
return c;
}
AbstractVehicle* ConcreteJet::clone() const
{
auto j = new ConcreteJet;
j->setSomething(this->getSomething());
return j;
}
And here's what I would like:
AbstractVehicle* AbstractVehicle::clone() const
{
auto v = new this->ACTUAL_CLASS;
v->setSomething(this->getSomething());
return v;
}
Is there any straightforward way to do this? If not, I'd love to know why this is the case, because naively it does not seem like more of a challenge to implement than something like dynamic dispatch.
Upvotes: 3
Views: 800
Reputation: 7915
This may not be exactly what you was asking for; but this may achieve what you are after. What I have here is an abstract base class with three defined derived classes. I do not have a cloning method within the class that is of a virtual type, but what I do have is a template function that does the work for you.
#include <iostream>
#include <conio.h>
class Base {
public:
enum Type {
TYPE_1 = 0,
TYPE_2,
TYPE_3,
}; // Type
private:
Type m_eType;
public:
virtual ~Base() {}
Type getType() const { return m_eType; }
protected:
explicit Base( Type eType ) : m_eType( eType ) {}
private:
Base( const Base& c ); // Not Implemented
Base& operator=( const Base& c ); // Not Implemented
}; // Base
class Derived1 : public Base {
private:
int m_val;
public:
Derived1() : Base( TYPE_1 ), m_val( 0 ) {}
explicit Derived1( int val ) : Base( TYPE_1 ), m_val( val ) {}
virtual ~Derived1() {}
int getValue() const { return m_val; }
void setValue( int val ) { m_val = val; }
private:
Derived1( const Derived1& c ); // Not Implemented
Derived1& operator=( const Derived1& c ); // Not Implemented
}; // Derived1
class Derived2 : public Base {
private:
float m_val;
public:
Derived2() : Base( TYPE_2 ), m_val( 0 ) {}
explicit Derived2( float val ) : Base( TYPE_2 ), m_val( val ) {}
virtual ~Derived2() {}
float getValue() const { return m_val; }
void setValue( float val ) { m_val = val; }
private:
Derived2( const Derived2& c ); // Not Implemented
Derived2& operator=( const Derived2& c ); // Not Implemented
}; // Derived2
class Derived3 : public Base {
private:
double m_val;
public:
Derived3() : Base( TYPE_3 ), m_val( 0 ) {}
explicit Derived3( double val ) : Base( TYPE_3 ), m_val( val ) {}
virtual ~Derived3() {}
double getValue() const { return m_val; }
void setValue( double val ) { m_val = val; }
private:
Derived3( const Derived3& c ); // Not Implemented
Derived3& operator=( const Derived3& c ); // Not Implemented
}; // Derived3
// This Function Template Takes The Base Of The Object And The
// Derived Type For Its Templated Parameters. The Function's
// Parameter Takes A Pointer To The Derived Type And A Bool Flag That Is
// True By Default And Will Make A Complete Clone, If This Is False It Will
// Make A Fresh Clean Object, It Will Construct
// A New Object On The Heap, Then Dynamically Cast It To A Pointer Of
// Its Base And It Will Return Its Base Pointer, Otherwise It Will
// Return nullptr. NOTE: I Did Not Include Any Error Checking To
// See If The Two Templated Types Passed In Are Derived From One Another.
template<class BaseT, class DerivedT>
BaseT* clone( DerivedT* clonerPtr, bool bClone = true ) {
BaseT* basePtr = nullptr;
if ( bClone ) {
DerivedT* pTemp = new DerivedT();
pTemp = clonerPtr; // Will Require operator=() to be defined for the derived classes.
if ( pTemp == nullptr ) {
return nullptr;
}
basePtr = dynamic_cast<BaseT*>( pTemp );
} else {
// Create New Empty Object.
clonerPtr = new DerivedT();
if ( clonerPtr == nullptr ) {
return nullptr;
}
basePtr = dynamic_cast<BaseT*>( clonerPtr );
}
return basePtr;
}
int main() {
Base* pBase = nullptr;
// 1st Case Using The Heap
Derived1* ptr1 = new Derived1( 5 );
pBase = clone<Base, Derived2>( ptr1 );
std::cout << pBase->getType() << std::endl;
if ( ptr1 ) {
delete ptr1;
ptr1 = nullptr;
}
if ( pBase ) {
delete pBase;
pBase = nullptr;
}
// 2nd Case Using Stack Object
pBase = nullptr; // Not need but just incase
Derived2 d2( 3.41f );
pBase = clone<Base, Derived2>( &d2 );
std::cout << pBase->getType() << std::endl;
if ( pBase ) {
delete pBase;
pBase = nullptr;
}
std::cout << "Press any key to quit." << std::endl;
_getch();
return 0;
}
Another reminder is this; the template function nor the classes does any clean up of memory on the heap via the new and delete operator. So care needs to be used with this method or approach. If one wanted to achieve a similar approach but doesn't have to worry about cleaning up dynamic memory then the use of smart pointers should be used within the function template.
Upvotes: 0
Reputation: 32762
Depending on hour your setSomething/getSomething functions are defined, you may be able keep a single clone, and just have overrides on a 'create me' type function. For example:
AbstractVehicle* ConcreteCar::MakeNew() { // virtual method
return new ConcreteCar();
}
This would be called from the base class clone:
AbstractVehicle* AbstractVehicle::Clone() {
auto j = MakeNew();
j->setSomething(this->getSomething());
return j;
}
Upvotes: 1
Reputation: 42909
There's no straightforward way to do this. However, (I might get hanged for this) you can avoid writing multiple definitions of your clone
functions by using a pre-processor define as below:
#define DEFINE_VEHICLE_CLONE_MEMFUN(Vehicle) \
AbstractVehicle* Vehicle::clone() const { \
auto v = new Vehicle; \
v->setSomething(this->getSomething()); \
return v; \
}
And then define the member function in code as:
DEFINE_VEHICLE_CLONE_MEMFUN(ConcreteCar)
DEFINE_VEHICLE_CLONE_MEMFUN(ConcreteJet)
Upvotes: 1
Reputation: 36607
No. operator new
requires the type of the object(s) it is creating to be specified at compile time. What you are asking for requires the type of object to be created to be unknown at compile time, and worked out (or otherwise obtained) at run time.
Upvotes: 2