Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Unsmart Pointers: Part II


September, 2005: Unsmart Pointers: Part II

Christopher Diggins is a freelance computer programmer and developer of Heron. He can be contacted at [email protected].


In the previous installment of "Agile C++" [1], I introduced the concept of unsmart pointer class templates, covering the two basic kinds of unsmart pointers—deletable and undeletable. An unsmart pointer is a pointer that does not automatically delete the resource to which it refers. The deletable unsmart pointer provides a Delete() method for manual deletion of the referred resource, while the undeletable pointer does not. In this installment, I introduce some specialized deletable unsmart pointer class templates.

The any_ptr pointer class template can point to any copy-constructible value and clone it or delete it. The base_class_ptr can refer to any object derived from a class without a virtual destructor in a polymorphic manner (for example, through the base class) and ensure that the derived destructor is called. Finally, the counted_ptr unsmart point can help detect and prevent memory management errors such as double deletion, premature deletion, and orphaned memory blocks.

Unsmart Pointers: Recap

Pointers have different characteristics depending on how they are being used. For instance, a pointer might represent:

  • The return value of an address-of operation on a local variable.
  • A position in an array.
  • A dynamically allocated object.

In the first case, incrementing or decrementing the pointer would generally not be a safe thing to do, nor would calling delete on it. In the second case, incrementing or decrementing the pointer would be fine, but calling delete would not. In the third case, incrementing or decrementing would not be a safe thing to do, but calling delete would not only be fine, but expected. These are generalizations, and there are exceptions to these rules of thumb simply because C++ doesn't distinguish different types of pointer.

The purpose of unsmart pointers is to assign separate types to pointers based on their behavior. I decided to divide pointers up into two actual types—pointers that allow deletion and pointers that do not allow deletion. This lets you use the type system to enforce correct and intentional usage of pointers, such as prevention of calling delete on undeletable pointers by preventing inappropriate casts (for example, from undeletable pointers to a deletable pointer).

The implementation I proposed for deletable and undeletable pointers was a single template with two specializations: ptr<T, deletable> and ptr<T, !deletable>. To enforce the guarantee that undeletable pointers are in fact undeletable, I presented several strict type-casting rules. Listing 1 shows the various legal and illegal casts. I have made some minor improvements to the implementation of the ptr templates; see Listing 2.

Referring to [Almost] Any Object

The old school method of referring to an arbitrary object is through the much maligned void* type. The main problem with this technique is that it gives up automatic type checking, forcing you to devise a system for keeping track of type information. Fortunately, this bookkeeping can often be handled by a library: Listing 3 presents the unsmart pointer any_ptr, which can refer to any copy-constructible object, clone it, delete it, and get the type_info for the referenced type.

This class is similar to the boost::any type from the Boost library by Kevlin Henney. The difference is that any_ptr holds a pointer to a value, and does not make a copy of the value upon assignment. This makes any_ptr a complementary type to boost::any, and opens up some interesting possibilities for referring to collections or other objects with relatively expensive copy semantics.

It is an application of the technique addressed in my September 2004 CUJ article [2]. Conceptually, what is occurring is that at compile time, whenever an object is bound to an any_ptr, a function table is created containing three primary functions: Clone(), Delete(), and GetType(). What is stored within any_ptr is a void pointer, but the generated functions cast it to its true type, before operating on it. As a result, any_ptr has two pointers, one to the current object (stored as a void*) and the other to the statically generated function table.

Deleting Types Derived From Classes Without Virtual Destructors

Public inheritance has a well-known yet easily overlooked pitfall: When destroying a class derived from a base class without a virtual destructor, the behavior is undefined. In the best case, the derived destructor would simply not be called, but there are potentially greater repercussions as Pete Becker has pointed out [3]:

In many implementations, the effect is that the base class's destructor runs but the derived class's destructor doesn't, but that's not required. Even then, in some implementations the destructor also deletes the memory, and calling the "wrong" destructor can also mean that the wrong memory size is passed to the memory manager. And when the base is one of multiple bases, the pointer passed to the memory manager can be invalid (all but one of the bases is at some offset from the allocated memory block).

The obvious and common solution is to make ~MyBaseClass() virtual, but for various reasons, that is not always possible or appropriate.

For such situations, I have provided an implementation in Listing 4 of a base_class_ptr unsmart pointer that lets you refer to a type polymorphically and still assure that the derived class's constructor is called. The base_class_ptr is implemented using virtually the same technique as described for any_ptr.

Reference-Counted Pointers

Whenever I mention reference counting, people often assume I am referring to a somewhat naïve garbage-collection algorithm. This is, of course, not the only use for reference counting. Reference counting can also be used to detect errors, such as premature resource deletion and memory leakage at debug time, as demonstrated by the template counted_ptr in Listing 5. By using reference counting, you can detect when a deletion is occurring prematurely by simply asserting that the reference count is precisely one. This would indicate that another counted_ptr still refers to the object. Whether or not that will delete the referenced object that they both share, we cannot say. However, it is a definite possibility and it tells you that you should release the other counted_ptr first; after all, it is just good manners.

You can also check for many cases of orphaned memory blocks by asserting that the reference count never reaches zero outside of an explicit deletion. This would indicate to you that no other counted_ptrs will exist to the object, after the current reference-counted pointer is removed. The caveat for this method is that self-referential blocks are not caught by this algorithm, so this algorithm does not provide complete safety from orphaned memory, but every little bit helps.

The reference-counted counted_ptr template in Listing 5 fits into the unsmart pointer framework as a special case of a deletable pointer. It provides a Delete() member function and can be assigned to any undeletable pointer. It can only be assigned from a new_ptr or another counted_ptr. Note that this pointer cannot be assigned to another deletable pointer; that would invalidate all of the assertions, and would defeat the whole utility of the reference-counted error detection.

Be aware that the reference-counted unsmart pointer I am proposing does not provide a guarantee that it will catch all invalid usages. For instance, if you assign the reference-counted pointer to a noncounted pointer, usage of the resource after deletion is still possible. When the NDEBUG macro is defined, the pointer removes all reference-counting checks and provides performance nearly at par with a raw pointer.

Acknowledgments

My sincere gratitude goes out to Jonathan Turkanis for reviewing this article, Pete Becker for his comments and suggestions, and David Abrahams for posting to comp.std.c++ the improved technique for creating static function pointer tables at compile time, which is used in this article.

References

  1. [1] Diggins, Christopher. "Agile C++." C/C++ Users Journal, July 2005.
  2. [2] Diggins, Christopher. "C++ With Interfaces." C/C++ Users Journal, September 2004, http://www.cuj.com/documents/s=8188/cuj0409f/0409diggins.html?temp=aMWH5NYCmF.
  3. [3] Personal correspondence.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.