You Instantiate What You Use





Item 61. You Instantiate What You Use

In both C and C++, if you don't call a declared function (or take its address), you don't have to define it. An analogous situation occurs with member functions of class templates; if you don't actually call a template's member function, it's not instantiated.

This is clearly a handy property for the purpose of reducing code size. If a class template defines a large number of member functions, but you use only two or three of them, you don't pay the code space penalty for all those unused functions.

An even more important result of this rule is that you can specialize class templates with arguments that would be illegal if all the member functions were instantiated. With this rule in place, it's possible to write flexible class templates that can work with a wide variety of arguments, even if some arguments would result in erroneous instantiations of some member functions; if you don't actually call those erroneous functions, they're not instantiated, and you don't get an error. This is consistent with many areas of the C++ language, where potential problems are not flagged as errors until they become actual problems. In C++, it's OK to think illegal thoughts as long as you don't act on them!

Consider a simple, fixed-size array template:

template <typename T, int n>
class Array {
  public:
    Array() : a_( new T[n] ) {}
    ~Array() { delete [] a_; }
    Array( const Array & );
    Array &operator =( const Array & );
    void swap( Array &that ) { std::swap( a_, that.a_ ); }
    T &operator []( int i ) { return a_[i]; }
    const T &operator []( int i ) const { return a_[i]; }
    bool operator ==( const Array &rhs ) const;
    bool operator !=( const Array &rhs ) const
        { return !(*this==rhs); }
  private:
    T *a_;
};

This container behaves pretty much like a predefined array, with the usual operations for indexing, but it also provides some higher-level operations that are not available on predefined arrays, like swapping and comparison for equality (we've left out the relational operators for reasons of space). Let's look at an implementation of operator ==:

template <typename T, int n>
bool Array<T,n>::operator ==( const Array &that ) const {
    for( int i = 0; i < n; ++i )
        if( !(a_[i] == that.a_[i]) )
            return false;
    return true;
}

We know that both arrays being compared have the same number of elements, since they're both the same type and the array size is one of the template parameters, so we just have to perform a pairwise comparison of each element. If any pair of elements differs, the Array objects are not equal.

Array<int,12> a, b;
//...
if( a == b ) // calls a.operator ==(b)
    //...

When we use the == operation on our Array<int,12> objects, the compiler instantiates Array<int,12>::operator ==, which compiles correctly. If we hadn't used == (or !=, which calls operator ==) on objects of type Array<int,12>, then we would not have instantiated that member function.

The interesting situation occurs when we instantiate Array with a type that does not have an == operation defined. For instance, let's assume that our Circle type does not define or inherit an operator ==:

Array<Circle,6> c, d; // no problem!
//...
c[3].draw(); // OK

So far, so good. We have not directly or indirectly used an == operation on an Array<Circle,6> object, so the operator == function is not instantiated, and there is no error.

if( c == d ) // error!                        

Now we have a problem. The compiler will attempt to instantiate Array<Circle,6>::operator ==, but the function implementation will attempt to compare two Circle objects with a nonexistent == operator. Compile-time error.

This technique is commonly used in the design of class templates that are as flexible as possible but no more so.

Note that this idyllic situation does not occur in the case of an explicit instantiation of a class template:

template Array<Circle,7>; // error!                        

This explicit instantiation directive tells the compiler to instantiate Array and all its members with the arguments Circle and 7, resulting in a compile-time error in the instantiation of Array<Circle,7>::operator ==. Well, you asked for it....


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows