An Introduction to C++ Programming - Part 11/13

From EDM2
Jump to: navigation, search
An Introduction to C++ Programming / Part
1 2 3 4 5 6 7 8 9 10 11 12 13

Written by Björn Fahller

Auto Pointer

[Note: the source code for this month is auto_ptr.h here. Ed.]

In the past two articles, we've seen how a simple smart pointer, called simply ptr<T> was used to make memory handling a little bit easier. That is the core purpose of all smart pointers. You can find a few things in common with them all. They're templates, they relieve you of the burden of remembering to deallocate the memory, their syntax resembles that of pointers, they aren't pointers, and they're dangerous if you forget that you're dealing with smart pointers.

While ptr<T> served its purpose, it's a bit too simplistic to be generally useful. For example there was no way to rebind an object to another pointer, or to tell it not to delete the memory (that too can be useful at times, as we will see later in this article.)

This article is devoted to the only smart pointer provided by the standard C++ library; the class template auto_ptr<T>.

The problem to solve

I do not know what the core issues where when the auto_ptr<T> was designed, but I know what problems the implementation provided does solve.

  • exception safety
  • safe memory area ownership transfer
  • no confusion with normal pointers
  • controlled and visible rebinding and release of ownership
  • works with dynamic types
  • pointer-like syntax for pointer-like behaviour

Let us have a look at each of these in some detail and compare with the previous ptr<T>.

Exception safety

In this respect auto_ptr<T> and ptr<T> are equal. They both delete whatever they point to in their destructor. The only thing that auto_ptr<T> has to offer over ptr<T>, with respect to exception safety, is that we can tell an auto_ptr<T> object that it no longer owns a memory area. This can be used for holding onto something we want to return in normal cases, but deallocate in exceptional situations. Here is a code fragment showing such a situation:

  void f(); // may throw something
  int* ptr()
  {
    // returns a newly allocated value on success,
    // or the 0 pointer on failure.
try { auto_ptr<int> p(new int(1)); f(); return p.release(); } catch (...) { return 0; } }

In the code above, an exception thrown from f results in the destruction of the auto_ptr<int> object p before the call of the release member function, which means that the object pointed to will be deleted.

If, however, f does not throw any exception, p.release() is called, and the value returned from there is passed to the caller. The member function release releases ownership of the memory area from the auto_ptr<int> object. The value returned is the pointer to the memory area. Since the object no longer owns the memory area, it will not be deallocated.

Safe memory area ownership transfer

This safety is achieved by cheating in the assignment operator and copy constructor. The reason I've quoted the names assignment and copy, is that the cheat is so bad that it's not really an assignment and definitely not a copy. Rather, both are ownership transfer operations. What happens is that they both modify the right hand side, by relieving it of ownership while accepting ownership itself. Below are some examples of this:

  // simple transfer
  auto_ptr<int> p1(new int(1));
  // p1 owns the memory area.
  auto_ptr<int> p2;
  // p2 doesn't own anything.
  p2 = p1;
  // now p2 owns the memory area, p1 doesn't.
  // p1 has become the 0 "pointer"
  auto_ptr<int> p3(p2);
  // Now it is p3 that owns the memory area, not
  // p1 or p2.

I think the above example speaks for itself. An important issue here for those of you who have used early versions of the auto_ptr<T> is that older versions did not become 0 pointers when not owning the memory area, but that is the behaviour set in the final draft of the C++ standard. Of course, the above program snippet is too simplistic to be useful. The properties of the auto_ptr<T> are more useful when working with functions, where it works as both documentation and implementation of ownership transfer. What do you think about this?

  auto_ptr<int> creation();
  void termination(auto_ptr<int> rip);
  void f()
  {
    auto_ptr<int> pi=creation();
    // It's now clear that we are responsible for
    // deletion of the memory area allocated. Even
    // if we chose to release ownership from "pi",
    // we must take care of deallocation somehow.

    ... // use pi for something

    termination(pi);

    // since we're sending it off as an auto_ptr<T>,
    // deletion is not our headache anymore, and since
    // the function "termination" wants an auto_ptr<T>
    // it wants the responsibility.
  }

One of the headaches of using dynamically allocated memory is knowing who is responsible for deallocating the memory at any given moment in a program's lifetime. The auto_ptr<T> makes that rather easy, as can be seen above. Any function that returns an auto_ptr<T> leaves it to the caller to take care of the deallocation, and any function that accepts an auto_ptr<T> requires ownership to work. If something goes wrong between calling creation and termination, such that termination ought not be called, we must take care of the deallocation, but since we have it in an auto_ptr<T> that is automatically done for us if we return or throw an exception.

This functionality is an advantage that auto_ptr<T> offer over ptr<T>, since the latter doesn't have any way of transferring ownership.

No confusion with normal pointers

Since the auto pointers have the behaviour outlined above, it is extremely important that they cannot accidentally be confused with normal pointers. This is done by explicitly prohibiting all implicit conversions between pointer types and auto_ptr<T> types. The erroneous code below shows how:

  auto_ptr<int> creator();
  void termination(auto_ptr<int>);
  void f()
  {
    int* p = creator();
    // illegal. An auto_ptr<T> cannot be implicitly
    // converted to a pointer.

    auto_ptr<T> ap=p;
    // illegal. A pointer cannot be implicitly
    // converted to an auto_ptr<T>. When creating a
    // new auto_ptr<T> object, use the constructor
    // syntax auto_ptr<T> ap(p);

    ap=p;
    // also illegal. If you want to rebind an
    // auto_ptr<T> object to point to something else,
    // use the "reset" member function; ap.reset(p).

    termination(p);
    // also illegal. The auto_ptr<T> required by the
    // termination function cannot be implicitly
    // created from the pointer.
  }

It is indeed fortunate that the first and last error above are illegal. Imagine the maintenance headaches you could get otherwise. What would the first mean? Would the implicit conversion from auto_ptr<T> to a raw pointer transfer ownership or not? All implementations I have seen where such implicit conversions are allowed do not transfer the ownership, which in the situation above means that the memory would be deallocated when the auto_ptr<T> object returned is destroyed (which it would be immediately after the conversion.) The last is as bad. What about this situation?

int i;
termination(&i);

Ouch! The function would attempt to delete the local variable. For both the result will be something that in standardese is called undefined behaviour, but which in normal English best translates to a crash now, a crash later, or generally funny behaviour (possibly followed by a crash later.) Well, since it is illegal, we do not have to worry about it.

The second error auto_ptr<int> ap=p; is perhaps a bit unfortunate since the intended behaviour is clear. That it is illegal comes as a natural consequence of banning the third situation ap=p which is not clear. ap might be declared somewhere far far away, so that in the code near the assignment it is not obvious if it is an auto_ptr<T> or a normal pointer.

In this respect auto_ptr<T> is better than ptr<T>, since ptr<T> does allow implicit construction, allowing the last error.

Controlled and visible rebinding and release of ownership

If we want to rebind an auto_ptr<T> object to another memory area, it is important that the memory area currently owned by the object (if any) is deallocated. The member function reset takes care of that. Calling ap.reset(p) will deallocate whatever ap owns (if anything) and make it own whatever p points to.

If we want a normal pointer from an auto_ptr<T> object, we can get it in two ways, depending on the desired effect. The member function release gives us a normal pointer to the memory area owned by the auto_ptr<T> object, and also gives us the ownership, so that we will be responsible for the deallocation. If we do not want that responsibility, but temporarily need a normal pointer to the memory area, we use the get member function. Here is an example showing the differences:

  void func(const int*); // may throw
  int* f(void)
  {
    auto_ptr<int> p(new int(1));
    func(p.get()); // call func with a normal pointer
    return p.release(); // return the pointer
  }

Above we see that the function func requires a normal pointer, but it does not assume ownership, so we use the get member to temporarily get the pointer and pass it to func. This function f then returns the raw pointer if func does its job, but if it fails with an exception, the auto_ptr<int> object p will deallocate the memory in its destructor.

Since ptr<T> was specifically designed to disallow transfer of ownership, this functionality is added-value for auto_ptr<T>.

Works with dynamic types

Just as a normal pointer to a base class can point to an object of a publicly derived class, an auto_ptr<T> can too. This is not particularly strange:

   class A {};
   class B : public A{};
   auto_ptr<A> pa(new B());
   auto_ptr<B> pb(new B());
   pa=pb;
   auto_ptr<A> pa2(pb);

The reverse is (of course) not allowed. For ptr<T> this is not a problem, since the functionality is only required if ownership transfer is allowed.

Pointer-like syntax for pointer-like behaviour

For the small subset of a pointer's functionality that is implemented in the auto_ptr<T> class template, the syntax is exactly the same. We get access to the element pointed to with operator* and operator->. This is the only functionality of a pointer that is implemented. Here it is a tie between auto_ptr<T> and ptr<T>, since the functionality is exactly the same and so is the syntax.

Implementation

The definition of auto_ptr<T> looks as follows:

  template <class T>
  class auto_ptr
  {
  public:
    explicit auto_ptr(T* t = 0) throw ();
    auto_ptr(auto_ptr<T>& t) throw();
    template <class Y> auto_ptr(auto_ptr<Y>& t) throw();
   ~auto_ptr() throw ();
    auto_ptr<T>& operator=(auto_ptr<T>& t) throw ();
    template <class Y>
      auto_ptr<T>& operator=(auto_ptr<Y>& t) throw ();
    T& operator*() const throw ();
    T* operator->() const throw ();
    T* release() throw ();
    void reset(T* t = 0) throw ();
    T* get(void) const throw ();
  private:
    T* p;
  };

Three new details can be seen above. The keyword explicit in front of the constructor, and template <class Y> inside the class definition. Both of these are relatively recent additions to the C++ language and far from all compilers support them. Third, and most important (please take note of this,) is that the copy constructor and assignment operator do take a non-const reference to their right hand side, so that it can be modified. explicit is what disallows implicit construction of objects, for example in function calls (see the error example above, when attempting to call a function requiring an auto_ptr<T> parameter with a normal pointer.) This keyword is, strictly speaking, not needed. There is a fake around it, which you will see when we get to the implementation details. The member templates, as the template <class Y> used inside the class definition is called, is a way of creating new member functions at need. This is what makes it possible to say auto_ptr<A> pa; auto_ptr<B> pb; pa=pb. With this mini-example, and the code above, we can see that a member function:

  auto_ptr<A>&
  auto_ptr<A>::operator=(auto_ptr<B>&) throw()

will be generated. If class B is publicly derived from class A, the generated code will compile just fine, otherwise we will get an error message from the compiler. This feature can, to the best of my knowledge, not be worked around. It is an essential addition to the C++ language. Unfortunately even fewer compilers support this than support the explicit keyword.

The code

Let us do the member functions one by one, beginning with the constructor. The only thing it needs to do is to initialize the auto_ptr<T> object such that it owns the memory area, and if it points to anything at all, it owns it (by definition.)

  template <class T>
  inline auto_ptr<T>::auto_ptr(T* ptr) throw()
   : p(ptr)
  {
  }

The inline keyword is new for this course, although it has been part of C++ for a very long time. Marking a function inline is a way of hinting to the compiler that you think this function is so simple that it can insert the function code directly where needed, instead of making a function call. This is just a hint; a compiler is free to ignore it, and likewise a good compiler may inline even functions not marked as inline (provided you cannot see any difference in the behaviour of the program.) Few compilers are smart enough to inline automatically, however, so there's a place for the inline keyword, and it will be used for all member functions of the auto_ptr<T>.

This constructor is marked explicit in the class definition. I mentioned above that the explicit keyword is not, strictly speaking, necessary. Here is the promised work-around:

  template <class T>
  class explicit
  {
  public:
    explicit(T t) : value(t) {};
    operator T() const { return value; };
  private:
    T value;
  };

  template <class T>
  inline auto_ptr<T>::auto_ptr(explicit<T*> ptr) throw()
   : p(ptr)
  {
  }

The way this works is as follows: By default, implicit conversions are allowed, but only one user defined implicit conversion may take place. It is an error if two or more implicit conversions are required to get the desired effect. Constructing an object is a user defined conversion, and executing a conversion operator is too. Look at this example usage:

  void termination(auto_ptr<int> pi);
  void func()
  {
    auto_ptr<int> pi(new int(1)); //**1 legal
    termination(new int(2)); //** 2 error
  }

The code at //**1 is not in error, because we say that we want an object of type auto_ptr<int*>. Since we've been so stern about this, we will be obeyed. It may seem like there are two implicit conversions taking place here, one for creating the explicit<T> object, and one for getting the value out of it, but that is not quite true. Our auto_ptr<int> accepts as its parameter an explicit<int*> which is implicitly created from the pointer value. Then, it is a detail of the innards of the auto_ptr<T> constructor how it is used, in this case to get the value from it.

The code at //**2 however, is in error, because the call to termination requires two user defined conversions. One from int* to explicit<int*>, and one from explicit<int*> to auto_ptr<int>.

Please see the provided source code for how to allow both versions to coexist for different compilers in the same source file.

  template <class T>
  inline auto_ptr<T>::auto_ptr(auto_ptr<T>& t) throw()
   : p(t.release())
  {
  }

There is not much strange going on here, except that the parameter is a non-const reference. As mentioned far above, the member release relieves the object of ownership and returns the pointer. Thus this constructor makes p point to what t did point to, and alters t so that it becomes the 0 pointer.

  template <class T> template <class Y>
  inline auto_ptr<T>::auto_ptr(auto_ptr<Y>& t) throw()
   : p(t.release())
  {
  }

The code for this constructor is, of course, the same as for the previous one. Note that both are necessary. I made a mistake with the auto_ptr<T> implementation available in the adapted SGI STL, that I thought the latter would imply the former. It doesn't, however, even though it may seem so.

Note, by the way, the syntax for a member template, with the two subsequent template <...>

Of course, users of compilers that do not implement member templates will get compilation errors on this member function. Please see the source code for how to work around the compilation error (the work around is simply not to have this member function, which means that the resulting auto_ptr will be limited in functionality.)

  template <class T>
  inline auto_ptr<T>::~auto_ptr() throw ()
  {
    delete p;
  }

If the object owns anything, it will be deleted by the destructor. Note that deleting the 0 pointer is legal, and does nothing at all. If the object does not own anything, p will be the 0 pointer.

  template <class T>
  inline auto_ptr<T>&
  auto_ptr<T>::operator=(auto_ptr<T>& t) throw ()
  {
    reset(t.release());
    return *this;
  }

  template <class T> template <class Y>
  inline auto_ptr<T>&
  auto_ptr<T>::operator=(auto_ptr<Y>& t) throw ()
  {
    reset(t.release());
    return *this;
  }

<code> This is pretty much the same story as the copy constructor. Since we are not creating a new object, however, we cannot just assign to p (it may point to something, in which case it must be deallocated.) The member function reset does exactly what we want; delete whatever p points to, and give it a new value. <code>

  template <class T>
  inline T& auto_ptr<T>::operator*() const throw ()
  {
    return *p;
  }

  template <class T>
  inline T* auto_ptr<T>::operator->() const throw ()
  {
    return p;
  }

These are not identical with the version of ptr<T> from the previous issue of the course. One word on the way, though. operator-> can, of course, only be used if T is a struct or class type. On some older compilers, it's even illegal to instantiate auto_ptr<T> if T is not a struct or class type. In most cases this is a minor limitation; after all, it is normally structs and classes you handle this way, and not built-in types.

  template <class T>
  inline T* auto_ptr<T>::release() throw ()
  {
    T* tp=p;
    p=0;
    return tp;
  }

Not much to say, is there? The object is relieved of ownership by making p the 0 pointer, and the value previously held by p is returned; just as mentioned in the introduction of the class.

  template <class T>
  inline void auto_ptr<T>::reset(T* t) throw ()
  {
    if (t != p) {
      delete p;
      p=t;
    }
  }

Deletes what it points to and sets p to the given value. Nothing strange, except the safety guard against resetting to the value already held. If we didn't have this guard, resetting to the current value would deallocate the memory and keep the ownership of it, for later deletion again! It seems like a better way is to just do nothing if the situation ever arises.

  template <class T>
  inline T* auto_ptr<T>::get(void) const throw ()
  {
    return p;
  }

Not much to say about this one.

Efficiency

The question of efficiency pops up now and then. How much does it cost, performance and memory-wise to use the auto_ptr<T> instead of ptr<T> from last month?

If you use auto_ptr<T> instead of ptr<T>, and use only the functionality that ptr<T> offers, the price is nothing at all. The constructor, destructor, operator* and operator-> holds exactly the same code for both templates. You pay for what you use only.

Compared to raw pointers and doing your own deletion? I do not know. It will depend a lot on how clever your compiler is with inlining. Most probably close to none at all. If you have a measurable speed difference in a real-world application, I would say the difference is that with auto_ptr<T> you do many more deletions (i.e. you have mended memory leaks you were not aware of having.)

Recap

The news this month were:

  • The standard class template auto_ptr<T> handles memory deallocation and ownership transfer.
  • Automatic memory deallocation and ownership transfer reduces the risk for memory leaks, especially when exceptions occur.
  • Implicit conversions between raw pointers and smart pointers is bad (even if it may seem tempting at first.)
  • The explicit keyword disallows implicit construction of objects.
  • The explicit keyword can be faked.
  • Member templates can be used to create member functions at compile time, just like function templates can be used to create functions at compile time.
  • inline hints to a compiler that you think a function is so small that it is better to directly insert the function code where required instead of making a function call.

Exercises

  • Why is it a bad idea to have arrays (or other collections) of auto_ptr<T>?
  • Can smart pointers be dangerous? When? auto_ptr<T> too?
  • What is a better name for this function template?
  template <class T>
  void funcname(auto_ptr<T>)
  {
  }

  • What happens if ~T throws an exception?

Coming up

If I missed something, or you want something clarified further or disagree with me, please drop me a line and I'll address your ideas in future articles.

Next month we'll have a look at a smarter pointer; a reference counted one. I'm beginning to dry up on topics now, however, so please write and give me suggestions for future topics to cover.