?
Solved

Destruction & RTTI queries

Posted on 2003-03-11
17
Medium Priority
?
314 Views
Last Modified: 2010-04-01
Hi,

I have three questions only requiring very short answers...

Question 1
**********
I have the following class definitions:

class Food
{
public:
      Food (char* food_name, int pr_t, int pa_t, int w_t = 0, int t_pr = 0, int t_pa = 0) : name(food_name),

processing_time(pr_t), time_processed(t_pr), packing_time(pa_t), time_packed(t_pa), waiting_time(w_t) {}      
      virtual ~Food() = 0;
      virtual Food* clone() = 0;
protected:                                                                                          
      char* name;
};


class Perishable_Food : public Food
{
public:
      Perishable_Food(char* p_f, int pr_t, int pa_t, int s_t, int w_t = 0) : Food(p_f, pr_t, pa_t, w_t), spoil_time(s_t) {}
      ~Perishable_Food() {};
      Food* clone();
private:
      int spoil_time;
};



class NonPerishable_Food : public Food
{
public:
      NonPerishable_Food(char* p_f, int pr_t, int pa_t) : Food(p_f, pr_t, pa_t) {}
      ~NonPerishable_Food() {};
      Food* clone();
};


Right, nothing to compicated there.  It has however only just come to my attention however that in the implementation for

const Perishable_Food& operator = (const Perishable_Food & rhs);

and

const NonPerishable_Food& operator = (const NonPerishable_Food & rhs);

I was allocating memory with 'new', as shown below

const NonPerishable_Food& NonPerishable_Food::operator = (const NonPerishable_Food & rhs)
{
        name = new char[strlen(rhs.name) + 1];
        strcpy(name, rhs.name);
      processing_time = rhs.processing_time;
      packing_time = rhs.packing_time;
      time_processed = rhs.time_processed;
      time_packed = rhs.time_packed;
      return *this;
      
}

const Perishable_Food& Perishable_Food::operator = (const Perishable_Food & rhs)
{
        name = new char[strlen(rhs.name) + 1];
        strcpy(name, rhs.name);
      processing_time = rhs.processing_time;
      packing_time = rhs.packing_time;
      time_processed = rhs.time_processed;
      time_packed = rhs.time_packed;
      spoil_time = rhs.spoil_time;
      return *this;
}

This is clearly needed as in the case of A=B, if A is destroyed B is left with a dangling pointer 'name', right ?  At least I

hope so.
The current implementation of the derived classes constructors is shown above in the definitions, and the destructor for the

Food class is currently

Food::~Food() {}

as it is pure virtual hence must have a body.

So finally to my question, after having allocated memory *possibly* (possibly as the assignment operator may no be called for

all Food or derived class objects) for name in the overloaded assignment operator, I need to explicily remove this with

'delete'.

??? But in which destructor should the call to 'delete name' go, and why ???

Question 2
**********

Also If I have the clone methods written as


// Returns a pointer to a new perishable food object with the same attributes as the 'this' pointer
Food* Perishable_Food::clone()
{      
      Perishable_Food* nf = new Perishable_Food(name, processing_time, packing_time, spoil_time, waiting_time);
      
      return nf;
}

// Returns a pointer to a new none perishable food object with the same attributes as the 'this' pointer
Food* NonPerishable_Food::clone()
{
      NonPerishable_Food* nf = new NonPerishable_Food(name, processing_time, packing_time);
      
      return nf;
}

??? Will these objects created by the calls to 'new' get deleted if I rewrite the destructor as in Q1; if not how should I go about deleting them ???

Question 3
**********

Different classes this time...

class Machine
{
public:
      inline Machine() { busy = false; current = 0; };
      friend inline ostream& operator << (ostream& os, Machine& M) { return os << M.thisQueue << endl;}
      virtual void tick() = 0;

protected:
      Food* current;
      Queue thisQueue;
      IRNG gen;
      bool busy;
};


// Processing machine definition; an abstract class.  
class ProcessingMachine : public Machine
{
public:
      ProcessingMachine(char* food_name, int pr_t, int pa_t, double p) : name(food_name), processing_time(pr_t), packing_time(pa_t), prob(p){}      
      void tick();
      virtual ~ProcessingMachine() = 0;
protected:
      char* name;
      int processing_time;
      int packing_time;
      double prob;
};

class PProcessingMachine : public ProcessingMachine
{
public:
      PProcessingMachine(char* f_n, double p, int st, int pr_t, int pa_t) : ProcessingMachine(f_n, pr_t, pa_t, p), spoil_time(st) {}
      void tick();  
private:
      int spoil_time;
};

class NPProcessingMachine : public ProcessingMachine
{
public:
      NPProcessingMachine(char* f_n, double p, int pr_t, int pa_t) : ProcessingMachine(f_n, pr_t, pa_t, p) {}
      void tick();
};


I then have in my test program

int main () {
      ProcessingMachine* ProcessingMachines[3];
      ProcessingMachines[0] = new PProcessingMachine("Cheese", 0.1, 45, 20, 20);            
      ProcessingMachines[1] = new PProcessingMachine("Blue Cheese", 0.1, 40, 20, 20);
      ProcessingMachines[2] = new NPProcessingMachine("Soup Powder", 0.1, 22, 20);      

      for (int j = 0; j < 3; j++) {
            ProcessingMachines[j]->tick();
      }

OK, I look at the output I find that, where 'i' is 0, the tick() routine for the PProcessingMachine *is* called, same for when 'i'is 1, and when 'i' is 2, the tick() routine of the NPProcessingMachine *is* called.  Dynamic Binding and RTTI right ?

This is obviously what I wanted, but closer analysis brought confusion when I realised that having declared an array of three pointers to *ProcessingMachines* (the second base class to PProcessingMachine and NPProcessingMachine, Machine being the first), this class having a *none virtual* tick() function, I should only be getting the ProcessingMachine tick() called, not the corresponding derived class versions !

??? So why am I getting the expected results ????

Many thanks in advance for any answers to these questions.
0
Comment
Question by:dazMan
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 8
  • 5
  • 4
17 Comments
 
LVL 9

Expert Comment

by:jasonclarke
ID: 8110975
Question 1:

Take a look at this article:

http://www.gotw.ca/gotw/059.htm

It explains the current best practise in C++ for what an assignment operator should look like.

Look at this bit in particular:

2. What is the canonical form of strongly exception-safe copy assignment?

> ??? But in which destructor should the call to 'delete name' go, and why ???

It must go in the base class destructor, but must also probably go in each assignment operator (or in a base class 'Swap' method).  Read the article, it may help you understand this too.  (The destructor will not be called by the assignment operator).
0
 
LVL 9

Expert Comment

by:jasonclarke
ID: 8111013
> Question 2

The destructor will never be called unless you specifically use 'delete' on the allocated object.

The thing to remember is that destructors will only be called explicitly for stack based objects and contained objects.

If a class contains a pointer then effectively only the pointer itself will be freed - any object pointed to will not be touched.
0
 

Author Comment

by:dazMan
ID: 8111053
Cheers thus far.

So what will happen to the object nf created with

NonPerishable_Food* nf = new NonPerishable_Food(name, processing_time, packing_time);

then ?  Will it never be deleted ?
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
LVL 9

Expert Comment

by:jasonclarke
ID: 8112009
> then ?  Will it never be deleted ?

correct ... it will not be deleted until somebody calls delete.

0
 
LVL 9

Expert Comment

by:jasonclarke
ID: 8112044
> Question 3

The reason is that in the base class Machine, the tick method is virtual.  Once a method is defined as virtual in a class hierachy it is always virtual regardless of what you do in derived classes.
0
 
LVL 12

Accepted Solution

by:
Salte earned 120 total points
ID: 8113335
Lots of stuff here..

Question 1.

First off, that destructor shouldn't be pure virtual and it is wrong that pure virtual functions must have a body. It is non pure virtual functions that must have a body if you plan to call them.

Secondly as to your question.

The name thing should be copied by a deep copy. So you must do that EVERY time.

1. In constructors (all of them).
2. In assignment operators (all of them).
3. Anywhere the name object is altered, you must make sure you point to the right value.

Each object must have its own name string and no object object must share that pointer.

so:
Food (char* food_name, int pr_t, int pa_t, int w_t = 0, int t_pr = 0, int t_pa = 0) : name(food_name), ...

is wrong.

You must do:

Food (char* food_name, int pr_t, int pa_t, int w_t = 0, int t_pr = 0, int t_pa = 0) : name(0), ...
{
   int len = strlen(food_name);
   name = new char[len + 1];
   strcpy(name,food_name);
   ...other code...
}

Now you might not want that 'int len' there and that is ok, you can call strlen() only once so:

   name = new char[strlen(food_name) + 1];
   strcpy(name,food_name);

works too.

Copy constructor likewise:

Food(const Food & src) : name(0)
{
   name = new char[strlen(src.name) + 1];
   strcpy(name,src.name);
}

Similarly with assignments. I guess you can figure out the drill by now.

It is of course the destructor for Food that must delete the string since that is the class that owns the string.

Food::~Food()
{ delete [] name; }

and that should answer Q2 as well.

Q3:

The answer is simple. The function tick() is virtual in all the classes.

If you make a class base:

class Base {
public:
   virtual int foo();
};

And then you make a dervied:

class Derived : public Base {
public:
   int foo();
};

Now, this doesn't say 'virtual' but since this function overrides the virtual int foo() it is also virtual. It is good practice to write 'virtual' in this case for Derived::foo() as well so that you don't fool someone who just browse through your code and doesn't notice that foo is virtual in Derived as well.

Note that if you made another derived:

class Another : public Base {
public:
   int foo() const;
};

This foo is NOT virtual. This function has a 'const' attribute attached to it and so it is different from Base::foo() and it is a completely unrelated function as far as C++ is concerned.

However, note that this will give compilation error:

class YetAnother : public Base {
public:
   double foo();
};

foo() matches the virtual foo - C++ doesn't care about return type when matching functions against each other. However, YetAnother::foo() returns double and not int as the baseclass function does and so C++ will complain.

Note that this IS valid and is also very useful:

class Base {
public:
   virtual Base * foo();
};

class Derived : public Base {
public:
   virtual Derived * foo();
};

Yes, the return type of Derived::foo() is different from Base::foo()'s return type but it can be converted to Base::foo()'s return type easily and so this particular exception to the above rule "return types must match" is included.

Hope this answer your questions.

Alf
0
 
LVL 12

Expert Comment

by:Salte
ID: 8113344
Btw, this has very little to do with RTTI. It has everything to do with vtables. RTTI is implemented by vtables also but RTTI per se has only to do with dynamic_cast<> and with typeid() operator.

Alf
0
 

Author Comment

by:dazMan
ID: 8126324
Thanks you two for the feedback; bare with me whilst I process all of your answers and come back with further problems if needed....

Cheers.
0
 

Author Comment

by:dazMan
ID: 8189645
Right, sorry about the delay.

Salte, in your answer you state that in the destructor for food, name should be deleted using

delete [] name;

Why is this ?  I thought that

delete name;

would do since name is not *declared* as an actual array (I know a string is an array in *theory*) ?

Apart from that the answer is fine.
0
 
LVL 12

Expert Comment

by:Salte
ID: 8189871
I assume that name is more than one single character and so you allocate name as:

name = new char[<some number here>];

if so you need to delete it using:

delete [] name;

When you use 'new T[N]' you do not allocate an object of type T, you allocate an array of N objects of type T and construct them using the default constructor.

so:

T * a = new T[100];

T * b = new T[1];

T * c = new T;

T * d = new T(100);

a is now an array of 100 elements, you can say a[0] and a[1], ... a[99]. a[100] would be outside and will not be well defined (will possibly make your program crash).

b is only one object but it is an array of 1 element, so you can say b[0] but not b[1] since that would be outside. It is an array which happens to have only one element.

c is a single object constructed by default constructor (same as the constructor that was used to create the elements in the arrays in a and b). However, this isn't an array but only one single element. You can still say c[0] though since the [] syntax works for pointers in C++ and C. However, few people use the [] syntax for c though and usually access the single element using c -> foo() instead of c[0].foo(); even though they are the same thing.

d is also a single object constructed by calling some constructor that takes an argument compatible with the value 100. Note the difference between a and d in that a uses square brackets while d uses regular parenthesis.

To delete these elements you do:

delete [] a;
delete [] b;
delete c;
delete d;

Note that it would be very wrong to say delete a since that would only call the destructor for the first element (and possibly not even that properly) while delete a is guaranteed to call destructor for all 100 elements of a.

delete c; would likewise be very wrong if you did delete [] c, since c isn't an array.

It is possible that the run time allocate arrays and single elements differently, the only thing you know for certain is that new T will give you a block of memory of sizeof(T) bytes while new T[20] will give you a block of memory of sizeof(T)*20 bytes.

However, the run time system must also at some place store a value that indicate how many elements you have and where they are stored. It is possible that an implementation therefore allcoates not sizeof(T)*20 bytes when you say new T[20] but rather allocates sizeof(T)*20 + sizeof(int) and then store the value 20 in that int location and then give you the remaining locations. When you then do delete [] a;' the run time will see that a is a pointer to an array and so expect to find that value 20 or 100 or whatever it was at that location so that it knows how many elements the array a holds.

While if you do 'delete a;' it only expect to have a pointer to a block of sizeof(T) data.

For this reason the 'delete [] p;' or 'delete p;' does not depend on what p points to in as much as it depends on how you allocated the data that p points to. If you allocated them as an array you must also delete them as an array and if you allocated the data as a single element you must also delete it as a single element.

Hope this explains.

Alf
0
 

Author Comment

by:dazMan
ID: 8189901
Both answers absolutely brilliant.  Thanks a lot Salte !
0
 

Author Comment

by:dazMan
ID: 8233945
Salte I know I have closed this question but I hope you can still tell me why, when following what you have said, I get the following errors:

'Food::Food' : redefinition of default parameter : parameter 6

(Repeated for parameters 5 and 4 of Food.cpp constructor):

//Food.cpp
Food::Food(char* food_name, int pr_t, int pa_t, int w_t = 0, int t_pr = 0, int t_pa = 0) : name(0), processing_time(pr_t), time_processed(t_pr), packing_time(pa_t), time_packed(t_pa), waiting_time(w_t)
{
     name = new char[strlen(food_name)+1];
     strcpy(name, food_name);
}

//Food.h
Food (char* food_name, int pr_t, int pa_t, int w_t = 0, int t_pr = 0, int t_pa = 0);

Thanks.
0
 

Author Comment

by:dazMan
ID: 8233971
Also will the objects created with 'new' in the clone method get delted by the rewritten destructor ?
0
 
LVL 12

Expert Comment

by:Salte
ID: 8234040
No,

if you clone an object you get a pointer. A destructor will typically destroy inner objects but the topmost object (the one pointed to by the cloned pointer) cannot be destroyed by the destructor, indeed the destructor is called when you delete that pointer.

class B {
public:
   virtual ~B() {}
   virtual B * clone() const = 0;
};

class A : public B {
private:
   char * m_name;

public:
   A(const A & a);

   virtual B * clone() const { return new A(*this); }
};

Now, if you have a

B * p1;

pointing to an A object and you do:

B * p2 = p1 -> clone();

You wlll get a second A object. This object is a separate object and unless you somewhere do

delete p2;

that object won't be deleted. Whatever you put in the destructor of A or B won't help that.

So, the destructor can only delete inner pointers, if the constructor created a char array on heap for m_p the destructor can delete [] m_p and if you have another object it can delete that too etc but the object of type A that contain those pointers in my example above the destructor cannot and should not delete it.

if the destructor did a 'delete this' it would be very wrong, for one thing the destructor do not know - and cannot know - if the object was created on heap and even if it was, the only way the destructor would be called in that case would be by doing a 'delete ptr' where ptr is a pointer to the object (well, you can call the destructor explicitely but that is 'advanced' usage and I won't go into that here), so since you already have called delete on the pointer to the object there's no need to delete it again by doing 'delete this'.

About the errors you get with default values, I assume you hvae done something like this:

class foo {
public:
   void bar(int k = 3);
};

void foo::bar(int k = 3)
{
   ...
}

This will cause an error like that, the reason is that you can't specify the default value both places, you should only specify the default value first place you mention the function (at the prototype), so if you correct the above to:

class foo {
public:
   void bar(int k = 3);
};

void foo::bar(int k)
{
}

The error should go away. I some times use a comment to indicate that a parameter has default argument but such comments shuold be used with caution. If you later change the default argument value the comment can quickly become desinformation instead of information.

Alf
0
 

Author Comment

by:dazMan
ID: 8234289
So just to clarify what you are saying Alf is that I do not need to to call delete on those objects created with new in

{    
     Perishable_Food* nf = new Perishable_Food(name, processing_time, packing_time, spoil_time, waiting_time);
     
     return nf;
}

?
0
 
LVL 12

Expert Comment

by:Salte
ID: 8234672
No,

If you call 'new' you must also 'delete' them.

Specifically, since the variable nf recive an object from heap by new Perishable_Food(....);

you must at some later time also delete that object. Maybe by delete nf; maybe by storing that pointer somewhere else and then later do delete on that pointer.

Also, if that constructor Perishable_Food::Perishable_Food(...) allocate any members using new, the destructor Perishable_Food::~Perishable_Food() should delete those same objects.

If A's constructor allocate something, A's destructor should delete it. If something is not allocated inside the object it shouldn't be deleted from the destructor of that object.

so:

class A {
private:
   X * m_xptr;
public:
   X() : m_xptr(0) {}
   X(int foo) { m_xptr = new X(foo); }
   ~X() { delete m_xptr; }
};

The member m_xptr is allocated in the class and the object 'belongs to' this object and so it is deleted by the destructor. This is true even if the default constructor doesn't allocate an object. It sets the pointer value to 0 but that's ok since if the pointer is 0 the delete won't delete anything.

However, the destructor does not delete the A object, that is done by whoever says 'new A' and not by the A class itself.

Hope this explains.

Alf
0
 

Author Comment

by:dazMan
ID: 8263031
Alf,

Thank you very much for the extended help.  I appreciate it greatly.
0

Featured Post

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
This article will show you some of the more useful Standard Template Library (STL) algorithms through the use of working examples.  You will learn about how these algorithms fit into the STL architecture, how they work with STL containers, and why t…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.
Suggested Courses

765 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question