The Artima Developer Community
Sponsored Link

The C++ Source
The Law of The Big Two
by Bjorn Karlsson and Matthew Wilson
October 1, 2004

<<  Page 3 of 3

Advertisement

RAII Saves the Day

The ubiquitous reference to RAII (Resource Acquisition Is Initialization [7]) should be justified in this case, because we come here seeking the essence of what RAII is, namely that the constructor of a local object handles the acquisition of a resource and its destructor releases it. Using this idiom means that it isn't possible to forget about releasing a resource; nor is it required to think about the subtle issues that you've just handled manually for the Example class. A simple wrapper class, intended mainly for the purpose of adding RAII to simple classes like SomeResource, might look like this:

template <typename T> class RAII {
  T* p_;
public:
  explicit RAII(T* p) : p_(p) {}

  ~RAII() {
    delete p_;
  }

  void reset(T* p) {
    delete p_;
    p_=p;
  }
  
  T* get() const {
     return p_;
  }

  T& operator*() const {
     return *p_;
  }

  void swap(RAII& other) {
    std::swap(p_,other.p_);
  }

private:
  RAII(const RAII& other);
  RAII& operator=(const RAII& other);
};
The only responsibilities this RAII class has are to store a pointer, return it when someone needs it, and properly delete it when destructed. Using such a class greatly simplifies the Example class; both now and when applying any future changes to it:
class Example {
  RAII<SomeResource> p_;
  RAII<SomeResource> p2_;
public:
  Example() :
    p_(new SomeResource()),
    p2_(new SomeResource()) {}

  Example(const Example& other)
    : p_(new SomeResource(*other.p_)),
      p2_(new SomeResource(*other.p2_)) {}

  Example& operator=(const Example& other) {
    // Self assignment?
    if (this==&other)
      return *this;

    *p_=*other.p_;
    *p2_=*other.p2_;
    return *this;
  }

  ~Example() {
    std::cout << "Deleting Example, freeing SomeResource!\n";
  }
};

You're basically back where you started with the original version, which hasn't a care with respect to exception safety. However, this time being oblivious to resource management and potential exceptions is intended, because it's already taken care of. This brings us to a very important point—the destructor now does nothing except write out a simple trace message:

~Example() {
  std::cout << "Deleting Example, freeing SomeResource!\n";
}

This means that one could (or even should!) remove the destructor, and rather rely on the compiler-generated version [8]. One of the Big Three is suddenly out of a job in the Example class! However, you must duly note that this simple example only handles raw pointers; there are many other resources than that in real-world programs. Although many of them provide clean-up services upon deletion (again, RAII in action), some don't or can't. This, too, can be solved without defining a destructor, which is the topic of the next section.

Note: Diligent readers may observe that the RAII class isn't exactly all it could be, but in fact, it doesn't have to, because a similar implementation already exists in the C++ Standard Library, namely std::auto_ptr. It basically works the way we've shown for the RAII class, only better. Why provide your own, then? Because auto_ptr defines a public copy constructor and copy assignment operator, both of which assume ownership of the resource, whereas you need it to be copied (the RAII class doesn't do that either, but at least it reminds you to do it [9]). You need to copy the resource, not have its ownership silently transferred, so for the sake of this example we're happy to reinvent the wheel.

Smart Pointers or Smart Resources?

What we've shown here in terms of resource management exists in virtually every smart pointer class (many thousand programmers think that the ones in Boost [10, 11] are especially fine examples of smart pointers). But as mentioned, resource management is not just about calling delete, it may require some other logic, or other means of freeing the resources (for example, calling close()). That's probably the reason why more and more smart pointer types are becoming smart resources; beyond supporting automatic deletion of dynamically allocated resources, they allow customization so that it is possible to have user-defined deleters called, or even defining the deallocation function inline (made possible through bind expressions and lambda expressions, as those found in Boost.Lambda [12]). Much of the code that one has previously put in the destructor of the aggregating class is now coupled more tightly with the resource (or resource holder), which makes perfect sense. It will be interesting to see what the future brings in this area. With multi-threading and exception-safety management that go far beyond what many of us previously were exposed to (at least that's true for the authors), intelligent resource management tools are becoming increasingly important.

Conclusion

The Law of the Big Three has played, and continues to play, an important role in C++. However, we think that there is good reason to leave the destructor out of both the discussion and the implementation when possible, leading to a derivative "Law of the Big Two". The reason is that often, unadorned pointers should be avoided as class members—to be replaced by smart pointers. Either way, the role of copy constructors and copy assignment operators is often forgotten or ignored; it's our hope that this article may help address that in some small way.

Acknowledgements

Notes and References

  1. One with Wasabi, the other a little Soy sauce and some pine kernels.
  2. C++ FAQs (Addison-Wesley 1998), by Marshall Cline, Greg Lomow, and Mike Girou.
  3. See Herb Sutter's GotW 59.
  4. More Exceptional C++ (Addison-Wesley 2001), by Herb Sutter.
  5. boost::noncopyable is documented here.
  6. Imperfect C++ (Addison-Wesley 2004), by Matthew Wilson.
  7. See Bjarne Stroustrup's C+ + Style and Technique FAQ.
  8. In some cases, for example when defining a base class, you'll want to declare the destructor virtual, to ensure that deletion of an instance of a derived class through a pointer to a base class works correctly. Of course, an empty implementation will suffice.
  9. A const std::auto_ptr would have done the sa me for the examples in this article.
  10. More about Boost at www.boost.org
  11. Boost.Smart_ptr is documented at http://www.boost.org/libs/smart_ptr/smart_ptr.htm
  12. Boost.Lambda is documented at http://www.boost.org/libs/lambda/doc/index.html
  13. Some readers may wonder why there was no mention of function-try-blocks in the article. Well, that was the conclusion of the discussions: for the topics depicted herein, do not resort to using function-try-blocks. Even though many modern compilers will allow you to manipulate member variables in the catch clauses of function-try-blocks, and POD types such as our pointers keep their values—whether initialized or uninitialized—from the epoch of the exception thrown in the ctor-initializer, any reliance on such behavior is a violation of the standard and is non-portable. The fact that it's also mind-bendingly confusing puts the cherry on the cake. Just say no!

About the Authors

Matthew Wilson is a software development consultant for Synesis Software, and creator of the STLSoft libraries. He is author of the forthcoming book Imperfect C++ (Addison-Wesley, 2004), and is currently working on his next two books, one of which is not about C++. Matthew can be contacted via http://imperfectcplusplus.com/.

Bjorn Karlsson is proud to be a C++ designer, programmer, teacher, preacher, and student. He has finally learned enough about C++ to realize how little he knows. When not reading or writing articles, books, or code, he has the privilege to be a part of the Boost community, and a member of The C++ Source Advisory Board. His book, Beyond The C++ Standard Library: An Introduction to Boost, will be published by Addison-Wesley in 2005. He appreciates it when people send him interesting emails at bjorn.karlsson@readsoft.com.

<<  Page 3 of 3


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us