Go back to Richel Bilderbeek's homepage.

Go back to Richel Bilderbeek's C++ page.

 

 

 

 

 

(C++) Answer of exercise #1: a foolproof function

 

This is the answer of exercise #1: a foolproof function.

 

 

 

 

 

1) prevent that Thing is modified

 

We do not want to modify a Thing. The compiler, however, does not know this. The compiler thinks we do want to modify a Thing, because the pointer to Thing is not const. This makes it possible to write to a Thing, as shown in the code below:

 

struct Thing { int mX; };

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(Thing * thing)
{
  thing->mX = 0; //Write to Thing! Must be noticed by the compiler
//Read Thing
}

 

To solve this problem, make Thing const:

 

struct Thing { int mX; };

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * thing)
{
  //thing->mX = 0; //Cannot modify a const object
  //Read Thing
}

 

Use const whenever possible [1-4].

 

 

 

 

 

2) prevent that Thing cannot be read

 

Before being read, pointers must always point to a valid object. Nothing prevents us from making the pointer-to-Thing point to zero:

 

struct Thing { int mX; };

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * thing)
{
  thing = 0;
  //Cannot read from pointer to Thing anymore
  //Read Thing
}

 

To solve this problem, make the pointer itself const as well. This brings you to the following code:

 

struct Thing { int mX; };

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * const thing)
{
  //thing = 0; //Cannot modify a const object
  //Read Thing
}

 

Again, use const whenever possible [1-4].

 

 

 

 

 

3) prevent that Thing is modified in some other way than #1

 

Be aware that you can delete a pointer-to-const. Deleting a Thing, however, is disastrous:

 

struct Thing { int mX; };

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * const thing)
{
  delete thing;
  //Read Thing
  //But there is no Thing anymore!
}

 

To solve this problem, we must modify Thing itself.

 

struct Thing
{
  int mX;

  private:
  ~Thing() {}
};

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * const thing)
{
  //delete thing; //Destructor for 'Thing' is not accessible
  //Read Thing
}

 

Now, exercise #1: a foolproof function has succeeded. If you knew all three steps, you get an A. Congratulations!

 

But...

 

 

 

 

 

3.1) A new problem

 

A new problem has arisen: we cannot construct a Thing, as it cannot be deleted when going out of scope:

 

struct Thing
{
  int mX;

  private:
  ~Thing() {}
};

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * const thing)
{
  //delete thing; //Destructor for 'Thing' is not accessible
  //Read Thing
}

int main()
{
  Thing t; //Destructor for 'Thing' is not accessible
}

 

Try to solve this problem yourself. Make it work. But keep ReadThing foolproof.

 

Got it?

 

Already?

 

Are you sure?

 

Okay, I'll show the solutions.

 

The way to solve this problem is to use the friend keyword. I show three possible friends:

  1. the main function
  2. a smart pointer
  3. boost::checked_delete

 

'What, use friend?', I hear you mutter, 'You should never use friend!', as well as 'friend reduces encapsulation!'. If you just muttered these words, feel free to contact me with a reference to the book in which you read this. I could not find it anywhere in my literature collection.

 

And also, if you muttered this, read the following three options below to see that there is more encapsulation, instead of less: Thing cannot be destroyed by anything, except its only, single friend. This makes Thing more encapsulated than by making everything able to destroy it. Read it again: making Thing destructable by its only, single friend only, makes Thing more encapsulated by making everything able to destroy it.

 

 

 

 

 

3.1.1) Befriending the main function

 

struct Thing
{
  int mX;

  private:
  ~Thing() {}
  friend int main();
};

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * const thing)
{
  //delete thing; //Destructor for 'Thing' is not accessible
  //Read Thing
}

int main()
{
  Thing t;
}

 

Befriending the main function has its restrictions: a Thing cannot be a class member. For this simple piece of code, however, it is a valid solution.

 

 

 

 

 

3.1.2) Befriending a smart pointer

 

#include <memory>

struct Thing
{
  int mX;

  private:
  ~Thing() {}
  friend class std::auto_ptr<Thing>;
};

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * const thing)
{
  //delete thing; //Destructor for 'Thing' is not accessible
  //Read Thing
}

int main()
{
  std::auto_ptr<Thing> t(new Thing);
}

 

A std::auto_ptr calls the destructor of Thing when it goes out of scope. So, when making a std::auto_ptr a friend of Thing, you can create std::auto_ptr<Thing>.

 

A drawback of this solution is that a std::auto_ptr does not have a checked delete (for example boost::checked_delete).

 

 

 

 

 

3.1.3) Befriending boost::checked_delete

 

#include <boost/shared_ptr.hpp>

struct Thing
{
  int mX;

  private:
  ~Thing() {}
  friend void boost::checked_delete<>(Thing *);
};

//Thing must be passed by normal (that is, non-smart) pointer
void ReadThing(const Thing * const thing)
{
  //delete thing; //Destructor for 'Thing' is not accessible
  //Read Thing
}

int main()
{
  boost::shared_ptr<Thing> t(new Thing);
}

 

My personal favorite solution. But also this solution has a drawback, as one can now write the following in ReadThing:

 

void ReadThing(const Thing * const thing)
{
  boost::checked_delete(thing);
  //Read Thing
  //But there is no Thing anymore!
}

 

But the exercise was to make ReadThing foolproof, not evil-genius-proof.

 

 

 

 

 

4) Document the internal assumptions of the function

 

The ReadThing function has one assumption: the pointer must point to a valid Thing. Use assert to document internal assumptions [5-9].

 

#include <cassert>

void ReadThing(const Thing * const thing)
{
  assert(thing);
  //Read Thing
}

 

Again, use assert to document internal assumptions [5-9].

 

 

 

 

 

References

 

  1. Bjarne Stroustrup. The C++ Programming Language (3rd edition). ISBN: 0-201-88954-4 7.9.3: 'Use const extensively and consistently'
  2. Scott Meyers. Effective C++ (3rd edition).ISBN: 0-321-33487-6. Item 3: 'Use const whenever possible'
  3. Jarrod Hollingworth, Bob Swart, Mark Cashman, Paul Gustavson. Sams C++ Builder 6 Developer's Guide. ISBN: 0-672-32480-6. Chapter 3: 'Understand and use const in your code'
  4. Jesse Liberty. Sams teach yourself C++ in 24 hours. ISBN: 0-672-32224-2. Hour 8, chapter 'Const member functions': 'Use const whenever possible.'
  5. Herb Sutter, Andrei Alexandrescu. C++ coding standards: 101 rules, guidelines, and best practices. ISBN: 0-32-111358-6. Chapter 68: 'Assert liberally to document internal assumptions and invariants'.
  6. Bjarne Stroustrup. The C++ Programming Language (3rd edition). 1997. ISBN: 0-201-88954-4. Advice 24.5.18: 'Explicitly express preconditions,postconditions, and other assertions as assertions'.
  7. Steve McConnell. Code Complete (2nd edition). 2004. ISBN: -735619670. Chapter 8.2 'Assertions', paragraph 'Guidelines for using asserts': 'Use assertions to document and verify preconditions and postconditions'.
  8. Steve McConnell. Code Complete (2nd edition). 2004. ISBN: -735619670. Chapter 8.2 'Assertions', paragraph 'Guidelines for using asserts': 'Use assertions for conditions that should never occur'.
  9. Jesse Liberty. Sams teach yourself C++ in 24 hours. ISBN: 0-672-32224-2. Hour 24, chapter 'assert()': 'Use assert freely'.

 

 

 

 

 

Go back to Richel Bilderbeek's C++ page.

Go back to Richel Bilderbeek's homepage.

 

Valid XHTML 1.0 Strict