ariya.io About Collections Archives

C++ Class and Preventing Object Copy

4 min read

clonetroopersIn some cases, an instance of a C++ class should not be copied at all. There are three ways to prevent such an object copy: keeping the copy constructor and assignment operator private, using a special non-copyable mixin, or deleting those special member functions.

A class that represents a wrapper stream of a file should not have its instance copied around. It will cause a confusion in the handling of the actual I/O system. In a similar spirit, if an instance holds a unique private object, copying the pointer does not make sense. A somehow related problem but not necessarily similar is the issue of object slicing.

The following illustration demonstrates a simple class Vehicle that is supposed to have a unique owner, an instance of Person.

class Car {
public:
  Car(): owner() {}
  void setOwner(Person *o) { owner = o; }
  Person *getOwner() const { return owner; }
  void info() const;
private:
  Person *owner;
};

For this purpose, the implementation of Person is as simple as:

struct Person {
  std::string name;
};

To show the issue, a helper function info() is implement as follows:

void Car::info() const
{
  if (owner) {
    std::cout < < "Owner is " << owner->name < < std::endl;
  } else {
    std::cout << "This car has no owner." << std::endl;
}

From this example, it is obvious that an instance of Car must not be copied. In particular, another clone of a similar car should not automatically belong to the same owner. In fact, running the subsequent code:

Person joe;
  joe.name = "Joe Sixpack";
 
  Car sedan;
  sedan.setOwner(&joe);
  sedan.info();
  Car anotherSedan = sedan;
  anotherSedan.info();

will give the output:

Owner is Joe Sixpack
Owner is Joe Sixpack

How can we prevent this accidental object copy?

Method 1: Private copy constructor and copy assignment operator

A very common technique is to declare both the copy constructor and copy assignment operator to be private. We do not even need to implement them. The idea is so that any attempt to perform a copy or an assignment will provoke a compile error.

In the above example, Car will be modified to look like the following. Take a look closely at two additional private members of the class.

class Car {
public:
  Car(): owner() {}
  void setOwner(Person *o) { owner = o; }
  Person *getOwner() const { return owner; }
  void info() const;
private:
  Car(const Car&);
  Car& operator=(const Car&);
  Person *owner;
};

Now if we try again to assign an instance of Car to a new one, the compiler will complain loudly:

example.cpp:35:22: error: calling a private constructor of class 'Car'
  Car anotherSedan = sedan;
                     ^
example.cpp:22:3: note: declared private here
  Car(const Car&);
  ^
1 error generated.

If writing two additional lines containing repetitive names is too cumbersome, a macro could be utilized instead. This is the approach used by WebKit, see its WTF_MAKE_NONCOPYABLE macro from wtf/Noncopyable.h (do not be alarmed, in the context of WebKit source code, WTF here stands for Web Template Framework). Chromium code, as shown in the file base/macros.h, distinguishes between copy constructor and assignment, denoted as DISALLOW_COPY and DISALLOW_ASSIGN macros, respectively.

Method 2: Non-copyable mixin

The idea above can be extended to create a dedicated class which has the sole purpose to prevent object copying. It is often called as Noncopyable and typically used as a mixin. In our example, the Car class can then be derived from this Noncopyable.

Boost users may be already familiar with boost::noncopyable, the Boost flavor of the said mixin. A conceptual, self-contained implementation of that mixin will resemble something like the following:

class NonCopyable
{
  protected:
    NonCopyable() {}
    ~NonCopyable() {}
  private: 
    NonCopyable(const NonCopyable &);
    NonCopyable& operator=(const NonCopyable &);
};

Our lovely Car class can be written as:

class Car: private NonCopyable {
public:
  Car(): owner() {}
  void setOwner(Person *o) { owner = o; }
  Person *getOwner() const { return owner; }
  }
private:
  Person *owner;
};

Compared to the first method, using Noncopyable has the benefit of making the intention very clear. A quick glance at the class, right on its first line, and you know right away that its instance is not supposed to be copied.

Method 3: Deleted copy constructor and copy assignment operator

For modern applications, there is less and less reason to get stuck with the above workaround. Thanks to C++11, the solution becomes magically simple: just delete the copy constructor and assignment operator. Our class will look like this instead:

class Car {
public:
  Car(const Car&) = delete;
  void operator=(const Car&) = delete;
  Car(): owner() {}
  void setOwner(Person *o) { owner = o; }
  Person *getOwner() const { return owner; }
private:
  Person *owner;
};

Note that if you use boost::noncopyable mixin with a compiler supporting C++11, the implementation of boost::noncopyable also automatically deletes the said member functions.

With this approach, any accidental copy will result in a quite friendlier error message:

example.cpp:34:7: error: call to deleted constructor of 'Car'
  Car anotherSedan = sedan;
      ^              ~~~~~
example.cpp:10:3: note: 'Car' has been explicitly marked deleted here
  Car(const Car&) = delete;
  ^

So, which of the above three methods is your favorite?

Related posts:

♡ this article? Explore more, check the archives, or follow me Twitter.

Share this on Twitter Facebook Google+

comments powered by Disqus