Item 6: Explicitly disallow the use of compiler-generated functions you do not want


Real estate agents sell houses, and a software system supporting such agents would naturally have a class representing homes for sale:

 class HomeForSale { ... }; 

As every real estate agent will be quick to point out, every property is unique no two are exactly alike. That being the case, the idea of making a copy of a HomeForSale object makes little sense. How can you copy something that's inherently unique? You'd thus like attempts to copy HomeForSale objects to not compile:

 HomeForSale h1; HomeForSale h2; HomeForSale h3(h1);               // attempt to copy h1   should                                   // not compile! h1 = h2;                          // attempt to copy h2   should                                   // not compile! 

Alas, preventing such compilation isn't completely straightforward. Usually, if you don't want a class to support a particular kind of functionality, you simply don't declare the function that would provide it. This strategy doesn't work for the copy constructor and copy assignment operator, because, as Item 5 points out, if you don't declare them and somebody tries to call them, compilers declare them for you.

This puts you in a bind. If you don't declare a copy constructor or a copy assignment operator, compilers may generate them for you. Your class thus supports copying. If, on the other hand, you do declare these functions, your class still supports copying. But the goal here is to prevent copying!

The key to the solution is that all the compiler generated functions are public. To prevent these functions from being generated, you must declare them yourself, but there is nothing that requires that you declare them public. Instead, declare the copy constructor and the copy assignment operator private. By declaring a member function explicitly, you prevent compilers from generating their own version, and by making the function private, you keep people from calling it.

Mostly. The scheme isn't foolproof, because member and friend functions can still call your private functions. Unless, that is, you are clever enough not to define them. Then if somebody inadvertently calls one, they'll get an error at link-time. This trick declaring member functions private and deliberately not implementing them is so well established, it's used to prevent copying in several classes in C++'s iostreams library. Take a look, for example, at the definitions of ios_base, basic_ios, and sentry in your standard library implementation. You'll find that in each case, both the copy constructor and the copy assignment operator are declared private and are not defined.

Applying the trick to HomeForSale is easy:

 class HomeForSale { public:   ... private:   ...   HomeForSale(const HomeForSale&);            // declarations only   HomeForSale& operator=(const HomeForSale&); }; 

You'll note that I've omitted the names of the functions' parameters. This isn't required, it's just a common convention. After all, the functions will never be implemented, much less used, so what's the point in specifying parameter names?

With the above class definition, compilers will thwart client attempts to copy HomeForSale objects, and if you inadvertently try to do it in a member or a friend function, the linker will complain.

It's possible to move the link-time error up to compile time (always a good thing earlier error detection is better than later) by declaring the copy constructor and copy assignment operator private not in HomeForSale itself, but in a base class specifically designed to prevent copying. The base class is simplicity itself:

 class Uncopyable { protected:                                   // allow construction   Uncopyable() {}                            // and destruction of   ~Uncopyable() {}                           // derived objects... private:   Uncopyable(const Uncopyable&);             // ...but prevent copying   Uncopyable& operator=(const Uncopyable&); }; 

To keep HomeForSale objects from being copied, all we have to do now is inherit from Uncopyable:

 class HomeForSale: private Uncopyable {     // class no longer   ...                                       // declares copy ctor or };                                          // copy assign. operator 

This works, because compilers will try to generate a copy constructor and a copy assignment operator if anybody even a member or friend function tries to copy a HomeForSale object. As Item 12 explains, the compiler-generated versions of these functions will try to call their base class counterparts, and those calls will be rejected, because the copying operations are private in the base class.

The implementation and use of Uncopyable include some subtleties, such as the fact that inheritance from Uncopyable needn't be public (see Items 32 and 39) and that Uncopyable's destructor need not be virtual (see Item 7). Because Uncopyable contains no data, it's eligible for the empty base class optimization described in Item 39, but because it's a base class, use of this technique could lead to multiple inheritance (see Item 40). Multiple inheritance, in turn, can sometimes disable the empty base class optimization (again, see Item 39). In general, you can ignore these subtleties and just use Uncopyable as shown, because it works precisely as advertised. You can also use the version available at Boost (see Item 55). That class is named noncopyable. It's a fine class, I just find the name a bit un-, er, nonnatural.

Things to Remember

  • To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations. Using a base class like Uncopyable is one way to do this.




Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
ISBN: 321334876
EAN: N/A
Year: 2006
Pages: 102

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net