Appendix A: Additional Example Code and Outputs

Additional code snippets and screen dumps are shown in this appendix. For best understanding of the material presented, read the rest of the text and follow the hyperlinks that lead here. Note that the SimpleString in these examples is the corrected version provided in this appendix. The corrected version does not cause dangling references.

The Corrected SimpleString

The corrected SimpleString class is used in subsequent examples in this appendix. See article three in the series for a detailed discussion of how SimpleString was fixed.

The simplestring.h file.

//*** SIMPLESTRING DECLARATION *** 

#ifndef EXAMPLE_SIMPLE_STRING
#define EXAMPLE_SIMPLE_STRING

class SimpleString {
  
public:

  explicit SimpleString(char* data = "");  //Use 'explicit' keyword to disable
                                           //automatic type conversions --
                                           //generally a good idea.

  //Copy constructor and assignment operator.
  SimpleString(const SimpleString& original);  
  SimpleString& operator=(const SimpleString& right_hand_side);

  virtual ~SimpleString();   //Virtual destructor, in case someone inherits
                             //from this class.

  virtual const char* to_cstr() const;  //Get a read-only C string.

  //Many other methods are needed to create a complete string class.
  //This example implements only a tiny subset of these, in order
  //to keep the discussion focused.

  //N.B. no 'inline' methods -- add inlining later, if needed for
  //optimization.

private:
  char* data_p_;   //Distinguish private class members: a trailing underscore
                   //in the name is one common method.
  
};

#endif

//*** END: SIMPLESTRING DECLARATION *** 

The simplestring.cpp file.

//*** SIMPLESTRING IMPLEMENTATION *** 

#include <cstring>

#include "simplestring.h"

using namespace std;


//Constructor 
SimpleString::SimpleString(char* data_p) :
  data_p_(new char[strlen(data_p)+1]) {
  strcpy(data_p_,data_p);
}


//Copy constructor.
SimpleString::SimpleString(const SimpleString& original) :
  data_p_(new char[strlen(original.data_p_)+1]) {
  strcpy(data_p_,original.data_p_);
}


//Assignment operator.
SimpleString& 
SimpleString::operator=(const SimpleString& right_hand_side) {
  
  //It is possible for the caller to request assignment to self 
  //(i.e. "a = a;").  Do nothing in this case, or a serious
  //error will result.
  if (this != &right_hand_side) {

    //Allocate a new buffer first.  If this fails (i.e. throws
    //an exception), everything is still consistent. 
    char* data_p = new char[strlen(right_hand_side.data_p_)+1];

    //Now, delete the old buffer, and start using the new one.
    delete [] data_p_;  //(1)
    data_p_ = data_p;   //(2)
    
    //Copy the data over from the right hand side.  We checked
    //before that this is not self assignment, so we are safe.
    //Otherwise, we would have already destroyed this data
    //in statements (1) and (2)!
    strcpy(data_p_,right_hand_side.data_p_);
  }

  //This allows assignments to be chained (i.e. "a = b = c = d;").
  return *this;
}

//Destructor
SimpleString::~SimpleString() {
  //N.B. Use of 'delete []' corresponds to previous use of 'new []'.
  //     Using just 'delete' here would be a disaster.
  delete [] data_p_;
}

//Returns a read-only C string representation.
const char* SimpleString::to_cstr() const {
  return data_p_;
}

//*** END: SIMPLESTRING IMPLEMENTATION *** 

A Base Class Used in Several Examples

//---
class Base {

public:

  Base();
  Base(const Base& original);
  virtual ~Base();  //N.B. virtual, so derived classes can override.
 
  void scare_me();  //Prints out a very scary message.

  void virtual example();

private:
  SimpleString scary_msg_;
  SimpleString* base_data_p_;

  //See the Training Wheels Class 
  //for an explanation of this declaration.
  Base& operator=(const Base& right_hand_side);

};

Base::Base() : 
  scary_msg_("Now formatting your hard drive ... just kidding!") {
  base_data_p_ = new SimpleString("Base Data");
}

//A proper copy constructor.
Base::Base(const Base& original) :
  scary_msg_("Now formatting your hard drive ... just kidding!") {
  base_data_p_ = new SimpleString(*original.base_data_p_);
}

Base::~Base() {
  cout << "Deleting Base Data" << endl;
  delete base_data_p_;
}


void Base::scare_me() {
  cout << scary_msg_.to_cstr() << endl;
}

void Base::example() {
  cout << base_data_p_->to_cstr() << endl;
}
//---

A Derived Class Used in Several Examples

//---
class Derived : public Base {

public:

  Derived();
  Derived(const Derived& original);
  virtual ~Derived();
  
  void virtual example();

private:
  SimpleString* derived_data_p_;

  //See the Training Wheels Class 
  //for an explanation of this declaration.
  Derived&  operator=(const Derived&  right_hand_side);
};


Derived::Derived() {
  derived_data_p_ = new SimpleString("Derived Data");
}

//A proper copy constructor for a derived class.
Derived::Derived(const Derived& original) : Base(original) {
  derived_data_p_ = new SimpleString(*original.derived_data_p_);
}

Derived::~Derived() {
  cout << "Deleting Derived Data, ";
  delete derived_data_p_;
}

void Derived::example() {
  cout << derived_data_p_->to_cstr() << ", ";
  Base::example(); //Specify the Base version of the virtual method.
  
}
//---

Object Slicing in Function Call: Example Output

Calling 'the_slicer' on a Base
Base Data
Deleting Base Data

Calling 'the_slicer' on a Derived
Base Data
Deleting Base Data

You can see the derived object being sliced in this output — the function being called takes its argument by value. The local copies of the argument are destroyed when the function exits, which accounts for the destructor calls.

The Non-Virtual Destructor: Example Classes

//---
class NVDBase {

public:

  NVDBase();
  ~NVDBase(); //N.B. Non-virtual.

private:
  SimpleString* base_data_p_;

  //See the Training Wheels Class 
  //for an explanation of these declarations.
  NVDBase(const NVDBase&  original);
  NVDBase&  operator=(const NVDBase&  right_hand_side);
};

NVDBase::NVDBase() {
  base_data_p_ = new SimpleString("Base Data");
}

NVDBase::~NVDBase() {
  cout << "Deleting Base Data" << endl << endl;
  delete base_data_p_;
}

class NVDDerived : public NVDBase {

public:

  NVDDerived();
  ~NVDDerived(); 

private:
  SimpleString* derived_data_;

  //See the Training Wheels Class 
  //for an explanation of these declarations.
  NVDDerived(const NVDDerived&  original);
  NVDDerived&  operator=(const NVDDerived&  right_hand_side);
};


NVDDerived::NVDDerived() {
  derived_data_ = new SimpleString("Derived Data");
}

NVDDerived::~NVDDerived() {
  cout << "Deleting Derived Data" << endl;
  delete derived_data_;
}
//---

The Non-Virtual Destructor: Example Output

Deleting Base Data

Deleting Derived Data
Deleting Base Data

Deleting Base Data

Using Arrays Polymorphically: Example Output

Base Data
Base Data
Base Data

Derived Data, Base Data
Segmentation fault

When an array of Base is used, everything works. Try this with an array of Derived, however, and there is a serious problem after the first object.

C vs. C++ Casts: the Example NotDerived Class

//---
class NotDerived {

public:

  NotDerived();
  virtual ~NotDerived(); 
  
  void soothe_me();

private:
  SimpleString lullaby_;

  //See the Training Wheels Class 
  //for an explanation of these declarations.
  NotDerived(const NotDerived&  original);
  NotDerived&  operator=(const NotDerived&  right_hand_side);
};


NotDerived::NotDerived() : lullaby_("Rock-a-bye-baby, etc., etc.") {}

NotDerived::~NotDerived() {}

void NotDerived::soothe_me() {
  cout << lullaby_.to_cstr() << endl;
}
//---

C vs. C++ Casts: Example Output

Value of 'wild_p' is now: 0xbfffd830
Now formatting your hard drive ... just kidding!

Value of 'wild_p' is now: 0x0
Segmentation fault

Bitwise Copying of Objects: Example Output

one two
 Segmentation fault

Only the initial printout of the two strings (before the function call) succeeds; any attempt to use the bitwise-copied objects causes a crash.

Freeing Memory that Was Not Dynamically Allocated: Example Output

I am a local object!
Segmentation fault

Exception Leaving a Constructor: Example Output

Deleting Derived Data, Deleting Base Data

This output shows what happens if someone tries to create a CtorThrow object. When the exception leaves the constructor, the object is not yet fully constructed, so the destructor is not invoked. Hence, the object pointed to by first_p_ — which has already been dynamically allocated — is leaked. The member_obj_, however, is a fully constructed member object; so it is properly destructed (you can see that both the base and derived parts of member_obj_ are handled correctly).

Exception Leaving a Destructor: Example Class

//---
class DtorThrow {
public:
  DtorThrow();
  ~DtorThrow();    //N.B. non-virtual, 
                   //not meant for subclassing.
private:

  //See the Training Wheels Class 
  //for an explanation of these declarations.
  DtorThrow(const DtorThrow&);
  DtorThrow& operator=(const DtorThrow&);

};

DtorThrow::DtorThrow() {}

DtorThrow::~DtorThrow() {
  //Could also call a function/method that throws.
  throw SimpleString("Exception!");
}
//---

Exception Leaving a Destructor: Example Output

Only one exception ...
Deleting Derived Data, Deleting Base Data
Deleting Base Data
Caught an exception

Exception during another exception ...
Aborted

Improper Throw: Example Output

Deleting Derived Data, Deleting Base Data   (1)
Derived Data, Base Data                     (2)
Deleting Derived Data, Deleting Base Data   (3)
Base Data                                   (4)
Deleting Base Data                          (5)
  1. Throwing a Derived; throwing always causes a copy, and the original is later deleted.

  2. The catch calls Derived's overloaded virtual example method — correct.

  3. The improper rethrow results in an additional copy of type Base; the Derived object is later deleted.

  4. The outer try block's catch gets the sliced object. Only the Base is left, so its example method is called.

  5. The Base object is cleaned up.

Improper Catch: Example Output

Deleting Derived Data, Deleting Base Data   (1)
caught a Base     
Base Data                                   (2)
Deleting Base Data                          (3)
Deleting Derived Data, Deleting Base Data   (4)
  1. Throwing a Derived; throwing always causes a copy, and the original is later deleted.

  2. The Base catch clause is invoked, because it is ahead of the Derived catch clause. The exception object is also sliced, due to catch-by-value.

  3. The Base class object (likewise resulting from the catch-by-value) is destroyed.

  4. The Derived object (which was actually thrown, and is still around) is destroyed.