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 2 of 3  >>

Advertisement

Disabling Copy Construction and Copy Assignment

It should be noted that there are times when copy construction and copy assignment do not make sense for a class. In those cases, it's trivial to disable them by using the common idiom of declaring the copy constructor and copy assignment operator private, as in:

class SelfishBeastie
{
  . . .

private:
  SelfishBeastie(const SelfishBeastie&);
  SelfishBeastie& operator=(const SelfishBeastie&);
};
An alternative is to use existing code; there's a class called boost::noncopyable [5] in the Boost libraries; inheriting from that class is nice as it documents that the class does not support copying and assignment (at least for all who are familiar with noncopyable!).
class SelfishBeastie
  : boost::noncopyable
{
  . . .

Another way of prohibiting copy construction and copy assignment is to make one or more members a reference or const (or const reference, for the especially cautious)—this effectively shuts down the compiler's ability to generate these special member functions. As Matthew discusses in detail in Imperfect C++ [6], this is not the preferred way of denoting that a class is not copyable, since it fails to adequately communicate the class's design to its users. It is, however, an excellent way to enforce design decisions; hence, it is a mechanism for communicating the original design decisions to future maintainers of the class, rather than a way of documenting semantics to the class's users. (Of course, with this technique all constructors of the class will need to initialize the reference members (as opposed to overwriting them within the constructor body), which is itself a good thing.)

The Big Three Aren't Enough

Although you've now come quite far in making the class Example behave properly, it's easy to go astray when exceptions come into play. Let's add another pointer to SomeResource in our Example class, like so:

class Example {
  SomeResource* p_;
  SomeResource* p2_;
public:
  Example() :
    p_(new SomeResource()),
    p2_(new SomeResource()) {
    std::cout << "Creating Example, allocating SomeResource!\n";
  }

  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";
     delete p_;
     delete p2_;
  }
};

Now consider what happens when an instance of Example is being constructed, and the second instance of SomeResource (pointed to by p2_) throws upon construction. One SomeResource, pointed to by p_, will already have been allocated, but still, the destructor will not be called! The reason is that from the compiler's point of view, the instance of Example has never existed, because the constructor never completed. This unfortunately means that Example is not exception-safe, due to a potential resource leak.

To make it safe, one practicable alternative is to move the initialization outside of the ctor-initializer, like so:

Example() : p_(0),p2_(0)
{
  try {
    p_=new SomeResource();
    p2_=new SomeResource("H",true);
    std::cout << "Creating Example, allocating SomeResource!\n";
  }
  catch(...) {
    delete p2_;
    delete p_;
    throw;
  }
}

Although you've managed to solve the issue of exception safety for the moment, this is not a very appealing solution, since we C++ programmers are weaned on initialization over assignment. As you'll soon see, an old and trustworthy technique comes to the rescue.

<<  Page 2 of 3  >>


Sponsored Links



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