confusion with example explanation of Prototype pattern in c++

192 views Asked by At

Some one posted the questions about this pattern but not having the solution of the question raised in my mind so posting with the queries i have...

In above example if all the instance are initialized by s_prototypes variable, and next time if any object of that will be replaced by new object in clone method then what will happen to existing object?? will it create memory leaks??

As far as I understand from above example two statement are confusing me

class Stooge {
public:
   virtual Stooge* clone() = 0;
   virtual void slap_stick() = 0;
};

class Factory {
public:
   static Stooge* make_stooge( int choice );
private:
   static Stooge* s_prototypes[4];
};

int main() {
   vector roles;
   int             choice;

   while (true) {
      cout << "Larry(1) Moe(2) Curly(3) Go(0): ";
      cin >> choice;
      if (choice == 0)
         break;
      roles.push_back(
         Factory::make_stooge( choice ) );
   }

   for (int i=0; i < roles.size(); ++i)
      roles[i]->slap_stick();
   for (int i=0; i < roles.size(); ++i)
      delete roles[i];
}

class Larry : public Stooge {
public:
   Stooge*   clone() { return new Larry; }
   void slap_stick() {
      cout << "Larry: poke eyes\n"; }
};
class Moe : public Stooge {
public:
   Stooge*   clone() { return new Moe; }
   void slap_stick() {
      cout << "Moe: slap head\n"; }
};
class Curly : public Stooge {
public:
   Stooge*   clone() { return new Curly; }
   void slap_stick() {
      cout << "Curly: suffer abuse\n"; }
};

Stooge* Factory::s_prototypes[] = {
   0, new Larry, new Moe, new Curly
};
Stooge* Factory::make_stooge( int choice ) {
   return s_prototypes[choice]->clone();
}

Output
Larry(1) Moe(2) Curly(3) Go(0): 2
Larry(1) Moe(2) Curly(3) Go(0): 1
Larry(1) Moe(2) Curly(3) Go(0): 3
Larry(1) Moe(2) Curly(3) Go(0): 0
Moe: slap head
Larry: poke eyes
Curly: suffer abuse

But when we call clone method by make_stooge method it will return new object,If that returns new object and replaces it with existing one then the existing object will create memory leak here(because new operator completely creates seperate object and not return existing one here)....

so this thing is confusing me with this example....

2

There are 2 answers

5
Serge Ballesta On

There is no memory leak in your code, because the newly created objects are carefully stored in a vector (here roles).

They are then used from that vector, and destroyed before the program ends:

   ...
   for (int i=0; i < roles.size(); ++i)
      delete roles[i];
}

But a return 0; before exiting main would be nicer...

And it is good practice to create a virtual destructor for classes intended to be derived: if any of Larry, etc. had a non trivial destructor, it would not be used when you delete them through a base class pointer:

class Stooge {
public:
   virtual Stooge* clone() = 0;
   virtual void slap_stick() = 0;
   virtual ~Stooge() {}
};

For the 4 static objects of s_prototypes, they are statically initialized at program startup before main is called, and they are never modified. Their clone method is called to create new objects, but s_prototype will still point to the old object - the reason why it is important that the newly created objects are stored and deleted.

There is indeed a memory leak for those 4 objects because they will never be explicitely destroyed. It is often considered as acceptable to leak that way static objects because their life time extends up to the program end, were all the memory is released. If it was important for the destructor to be called, you should also create static instances for them:

static Larry _larry;
static Moe _moe;
static Curly _curly;
Stooge* Factory::s_prototypes[] = {
   0, &_larry, &_moe, &_curly
};

If you make the destruction explicit with:

   virtual ~Stooge() {
       cout << "Delete " << this << endl;
   }

and slightly change the end of the program:

   for (int i=0; i < roles.size(); ++i)
      delete roles[i];
   cout << "END" << endl;
   return 0;
}

The output will be like:

...
Delete 001ADB78
END
Delete 00F350A0
Delete 00F3509C
Delete 00F35098

clearly showing that the static objects are destroyed after the end of the program.

0
David Scarlett On

Well, you are right that the pointers in s_prototypes are never deleted, but at least these objects are intended to last for the entire runtime of the program anyway, so unless there was a specific action that needed to be performed in their destructors, then it's not the end of the world if they aren't deleted before program termination. This is nowhere near as bad as code that has the potential to continuously leak memory, which would cause the memory used by the program to keep increasing.

If you wanted, you could replace them with static instances instead, like this:

#include <iostream>
#include <vector>
using namespace std;

class Stooge {
public:
   virtual Stooge* clone() = 0;
   virtual void slap_stick() = 0;
   virtual ~Stooge() = default;
};

class Larry : public Stooge {
public:
   Stooge*   clone() { return new Larry; }
   void slap_stick() {
      cout << "Larry: poke eyes\n"; }
};
class Moe : public Stooge {
public:
   Stooge*   clone() { return new Moe; }
   void slap_stick() {
      cout << "Moe: slap head\n"; }
};
class Curly : public Stooge {
public:
   Stooge*   clone() { return new Curly; }
   void slap_stick() {
      cout << "Curly: suffer abuse\n"; }
};

class Factory {
public:
   static Stooge* make_stooge( int choice );
private:
   static Larry larry;
   static Curly curly;
   static Moe moe;
};

int main() {
   vector<Stooge*> roles;
   int             choice;

   while (true) {
      cout << "Larry(1) Moe(2) Curly(3) Go(0): ";
      cin >> choice;
      if (choice == 0)
         break;
      roles.push_back(
         Factory::make_stooge( choice ) );
   }

   for (int i=0; i < roles.size(); ++i)
      roles[i]->slap_stick();
   for (int i=0; i < roles.size(); ++i)
      delete roles[i];
}

Stooge* Factory::make_stooge( int choice ) {
   switch(choice) {
   case 1:
      return larry.clone();
   case 2:
      return curly.clone();
   case 3:
      return moe.clone();
   default:
      return nullptr;
   }
}