Prototype Design Pattern
Prerequisites:
Overview:
Let us start with a very basic code with the custom class managing resources. This asks for the need of our own copy constructor to provide the deep copy and avoid issues related to memory.
#include <iostream>
class Test
{
public:
Test(int i) : m_a(new int(i)){}
~Test() {delete m_a;}
Test(const Test& obj) : m_a(new int(*obj.m_a)) {}
Test& operator=(const Test&) = delete;
Test(Test&& obj) noexcept = delete;
Test& operator=(Test&&) noexcept = delete;
void display() { std::cout << *m_a << std::endl; }
void setValue(int i) {*m_a = i;}
private:
int* m_a;
};
int main()
{
Test obj1(2);
Test obj2 = obj1;
obj1.display();
obj2.display();
obj2.setValue(22);
obj1.display();
obj2.display();
return 0;
}
And output of this code block is as below:
2
2
2
22
We can observe that copy constructor is making new copy on heap like constructor does and destructor releases the memory. This code does not result into segment fault due to double delete. Additionally, we don't observe object (obj1) unnecessarily updated due to update on the object (obj2). As we all know, this is nothing but the deep copy and it was required as our constructor deals with memory management on heap. If we would not have given our own copy constructor, compiler generated default copy constructor would make shallow copy. So far so good.
Memory management with polymorphic class:
Now, let us extend this heap memory management on inheritance hierarchy as below:
#include <iostream>
class Base
{
public:
Base(int i) : m_a(new int(i)){}
virtual ~Base() {delete m_a;}
Base(const Base& obj) : m_a(new int(*obj.m_a)) {}
Base& operator=(const Base&) = delete;
Base(Base&& obj) noexcept = delete;
Base& operator=(Base&&) noexcept = delete;
virtual void display() { std::cout << "Base Display" << std::endl; }
protected:
int* m_a;
};
class Derived : public Base
{
public:
Derived(int i,int j) : Base(i) ,m_b(new int(j)){}
virtual ~Derived() {delete m_b;}
Derived(const Derived& obj) : Base(*obj.m_a), m_b(new int(*obj.m_b)) {}
Derived& operator=(const Derived&) = delete;
Derived(Derived&& obj) noexcept = delete;
Derived& operator=(Derived&&) noexcept = delete;
void display() override { std::cout << "Derived Display" << std::endl; }
private:
int* m_b;
};
int main()
{
Base* pb1 = new Base(1);
pb1->display();
Base* pb2 = new Derived(2,3);
pb2->display();
delete pb1;
delete pb2;
return 0;
}
If we run the code above, it would produce output as below:
Base Display
Derived Display
This happens as pb2 is base class pointer pointing to derived class object and display method was marked virtual. Very simple and straight forward. Now, we generally have all the objects stored in some container (say vector) and iterate over it as below:
#include <iostream>
#include <vector>
class Base
{
public:
Base(int i) : m_a(new int(i)){}
virtual ~Base() {delete m_a;}
Base(const Base& obj) : m_a(new int(*obj.m_a)) {}
Base& operator=(const Base&) = delete;
Base(Base&& obj) noexcept = delete;
Base& operator=(Base&&) noexcept = delete;
virtual void display() { std::cout << "Base Display" << std::endl; }
protected:
int* m_a;
};
class Derived : public Base
{
public:
Derived(int i,int j) : Base(i) ,m_b(new int(j)){}
virtual ~Derived() {delete m_b;}
Derived(const Derived& obj) : Base(*obj.m_a), m_b(new int(*obj.m_b)) {}
Derived& operator=(const Derived&) = delete;
Derived(Derived&& obj) noexcept = delete;
Derived& operator=(Derived&&) noexcept = delete;
void display() override { std::cout << "Derived Display" << std::endl; }
private:
int* m_b;
};
int main()
{
std::vector<Base*> vec {new Base(1),new Derived(2,3),new Base(3)};
for(auto& pb : vec)
pb->display();
for(auto& pb : vec)
delete pb;
return 0;
}
Output of this code is as below:
Base Display
Derived Display
Base Display
As we can observe, this is very popular use case of the inheritance and polymorphism. All elements in vector containers are Base pointer but points to either derived class or base class object. Irrespective of knowing the actual data type, we can call display method and accordingly it calls either base or derived class method. But now can you handle the requirement to copy from existing object? Let us try as below:
Deep copy checking type:
#include <iostream>
#include <vector>
class Base
{
public:
Base(int i) : m_a(new int(i)){}
virtual ~Base() {delete m_a;}
Base(const Base& obj) : m_a(new int(*obj.m_a)) {}
Base& operator=(const Base&) = delete;
Base(Base&& obj) noexcept = delete;
Base& operator=(Base&&) noexcept = delete;
virtual void display() { std::cout << "Base Display" << std::endl; }
protected:
int* m_a;
};
class Derived : public Base
{
public:
Derived(int i,int j) : Base(i) ,m_b(new int(j)){}
virtual ~Derived() {delete m_b;}
Derived(const Derived& obj) : Base(*obj.m_a), m_b(new int(*obj.m_b)) {}
Derived& operator=(const Derived&) = delete;
Derived(Derived&& obj) noexcept = delete;
Derived& operator=(Derived&&) noexcept = delete;
void display() override { std::cout << "Derived Display" << std::endl; }
private:
int* m_b;
};
int main()
{
std::vector<Base*> vec {new Base(1),new Derived(2,3),new Base(3)};
std::vector<Base*> vecCopy;
for (auto& pb : vec)
{
if (dynamic_cast<Derived*>(pb))
vecCopy.push_back(new Derived(*dynamic_cast<Derived*>(pb)));
else
vecCopy.push_back(new Base(*pb));
}
for(auto& pb : vec)
pb->display();
for(auto& pb : vecCopy)
pb->display();
for(auto& pb : vec)
delete pb;
for(auto& pb : vecCopy)
delete pb;
return 0;
}
And output is as below:
Base Display
Derived Display
Base Display
Base Display
Derived Display
Base Display
Based on output , we can observe that we actually copied base pointer properly and it is actually doing proper (base pointer pointing to derived object or base object) copy. But can you find out the concern in the code related to copying the objects?
Yes, you figured it out correctly about the dynamic cast. First and foremost is the cost of the cast. It is too much and secondly, it is not very generic code and breaks open / close principle.
In fact, We should be able to copy the underlying object without being worried about the type of the object. Here comes prototype design pattern to our rescue.
Prototype Pattern:
Idea is very simple. We will make use of the virtual polymorphism. Clone method will be declared virtual into a base class and override it into derived class. This method will be responsible to make deep copy of the object as below:
Recommended by LinkedIn
#include <iostream>
#include <vector>
class Base
{
public:
Base(int i) : m_a(new int(i)){}
virtual ~Base() {delete m_a;}
Base(const Base& obj) : m_a(new int(*obj.m_a)) {}
Base& operator=(const Base&) = delete;
Base(Base&& obj) noexcept = delete;
Base& operator=(Base&&) noexcept = delete;
virtual Base* Clone() {return new Base(*this);}
virtual void display() { std::cout << "Base Display" << std::endl; }
protected:
int* m_a;
};
class Derived : public Base
{
public:
Derived(int i,int j) : Base(i) ,m_b(new int(j)){}
virtual ~Derived() {delete m_b;}
Derived(const Derived& obj) : Base(*obj.m_a), m_b(new int(*obj.m_b)) {}
Derived& operator=(const Derived&) = delete;
Derived(Derived&& obj) noexcept = delete;
Derived& operator=(Derived&&) noexcept = delete;
Base* Clone() override {return new Derived(*this);}
void display() override { std::cout << "Derived Display" << std::endl; }
private:
int* m_b;
};
int main()
{
std::vector<Base*> vec {new Base(1),new Derived(2,3),new Base(3)};
std::vector<Base*> vecCopy;
for (auto& pb : vec)
vecCopy.push_back(pb->Clone());
for(auto& pb : vec)
pb->display();
for(auto& pb : vecCopy)
pb->display();
for(auto& pb : vec)
delete pb;
for(auto& pb : vecCopy)
delete pb;
return 0;
}
It will also have same output as earlier. Refer below:
Base Display
Derived Display
Base Display
Base Display
Derived Display
Base Display
If we refer implementation of clone method, base class's clone method will be creating base class object on heap and derived class clone method will be creating derived class object on heap.
As clone method is virtual,
Thus , user don't have to do anything fancy and just call the clone method on base class pointer relying on the virtual mechanism to handle the cases appropriately. This is prototype design pattern for you.
When to use Prototype:
As we observed, prototype design pattern solves the deep copy problem for you. Problem arises when you wanted to copy object through base class pointer (holding base or derived class objects).
Let us dig into more details with very basic code as below:
#include <iostream>
#include <vector>
#include <memory>
class Base
{
public:
Base() {std::cout << "Ctor called\n";}
~Base() {std::cout << "Dtor called\n";}
Base(const Base& obj) {std::cout << "Copy Ctor called\n";}
Base& operator=(const Base&) = delete;
Base(Base&& obj) noexcept = delete;
Base& operator=(Base&&) noexcept = delete;
};
int main()
{
Base b1;//constructor
Base b2 = b1;//copy constructor
Base* pb1 = new Base;//constructor
Base* pb2 = pb1;//no constructor call
delete pb1;
return 0;
}
And output is as below:
Ctor called
Copy Ctor called
Ctor called
Dtor called
Dtor called
Dtor called
When object is assigned (b2 = b1), then copy constructor is called. Hence, we need not to worry if class is allocating resources as copy constructor will handle allocation.
But issue is in case of pointer. Code line (Base* pb2 = pb1) will not call the copy constructor and it is just like pb1 and pb2 will point to same object. But we needed extra object from current object. So, we needed to be cautious while playing with pointers.
Now, someone argue that why not call constructor instead from pointer as below:
#include <iostream>
#include <vector>
#include <memory>
class Base
{
public:
Base() {std::cout << "Base Ctor called\n";}
virtual ~Base() {std::cout << "Base Dtor called\n";}
Base(const Base& obj) {std::cout << "Base Copy Ctor called\n";}
Base& operator=(const Base&) = delete;
Base(Base&& obj) noexcept = delete;
Base& operator=(Base&&) noexcept = delete;
};
class Derived : public Base
{
public:
Derived() {std::cout << "Derived Ctor called\n";}
~Derived() {std::cout << "Derived Dtor called\n";}
Derived(const Derived& obj) {std::cout << "Derived Copy Ctor called\n";}
Derived& operator=(const Derived&) = delete;
Derived(Derived&& obj) noexcept = delete;
Derived& operator=(Derived&&) noexcept = delete;
};
int main()
{
Base* pb1 = new Derived;
std::cout << "-----------------\n";
Base* pb2 = new Derived(*(dynamic_cast<Derived*>(pb1)));
std::cout << "-----------------\n";
delete pb1;
delete pb2;
return 0;
}
This would have worked as well, but again it is type we needed to specify while constructing pb2 along with dynamic cast.
If we had used directly as below:
Base* pb2 = new Derived(*pb1);
It will not compile itself.
If we had used directly as below:
Base* pb2 = new Base(*pb1);
then pb2 will point to object of Base type. To overcome this issue and avoid using dynamic cast, we can take help of virtual mechanism to clone the object. That is Prototype pattern for you.
Whatever solution we discussed for final code of prototype design pattern is just a sample code. Base and Derived class are just the hierarchy . In general, it should be an interface which has pure virtual method Clone which will be implemented by all the derived classes which wanted prototype.
Feel free to share your views on the technical aspect of this discussion.
Good one, Thank you.
Thoughtful post, thanks Ketan