More Books
Effective C++ 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Table of Contents
Copyright
Praise for Effective C++, Third Edition
Addison-Wesley Professional Computing Series
Preface
Acknowledgments
Introduction
Terminology
Chapter 1. Accustoming Yourself to C++
Item 1: View C++ as a federation of languages
Item 2: Prefer consts, enums, and inlines to #defines
Item 3: Use const whenever possible
Item 4: Make sure that objects are initialized before they're used
Chapter 2. Constructors, Destructors, and Assignment Operators
Item 5: Know what functions C++ silently writes and calls
Item 6: Explicitly disallow the use of compiler-generated functions you do not want
Item 7: Declare destructors virtual in polymorphic base classes
Item 8: Prevent exceptions from leaving destructors
Item 9: Never call virtual functions during construction or destruction
Item 10: Have assignment operators return a reference to *this
Item 11: Handle assignment to self in operator=
Item 12: Copy all parts of an object
Chapter 3. Resource Management
Item 13: Use objects to manage resources.
Item 14: Think carefully about copying behavior in resource-managing classes.
Item 15: Provide access to raw resources in resource-managing classes.
Item 16: Use the same form in corresponding uses of new and delete.
Item 17: Store newed objects in smart pointers in standalone statements.
Chapter 4. Designs and Declarations
Item 18: Make interfaces easy to use correctly and hard to use incorrectly
Item 19: Treat class design as type design
Item 20: Prefer pass-by-reference-to-const to pass-by-value
Item 21: Don't try to return a reference when you must return an object
Item 22: Declare data members private
Item 23: Prefer non-member non-friend functions to member functions
Item 24: Declare non-member functions when type conversions should apply to all parameters
Item 25: Consider support for a non-throwing swap
Chapter 5. Implementations
Item 26: Postpone variable definitions as long as possible.
Item 27: Minimize casting.
Item 28: Avoid returning "handles" to object internals.
Item29: Strive for exception-safe code.
Item 30: Understand the ins and outs of inlining.
Item31: Minimize compilation dependencies between files.
Chapter 6. Inheritance and Object-Oriented Design
Item 32: Make sure public inheritance models "is-a."
Item 33: Avoid hiding inherited names
Item 34: Differentiate between inheritance of interface and inheritance of implementation
Item 35: Consider alternatives to virtual functions
Item 36: Never redefine an inherited non-virtual function
Item 37: Never redefine a function's inherited default parameter value
Item 38: Model "has-a" or "is-implemented-in-terms-of" through composition
Item 39: Use private inheritance judiciously
Item 40: Use multiple inheritance judiciously
Chapter 7. Templates and Generic Programming
Item 41: Understand implicit interfaces and compile-time polymorphism
Item 42: Understand the two meanings of typename
Item 43: Know how to access names in templatized base classes
Item 44: Factor parameter-independent code out of templates
Item 45: Use member function templates to accept "all compatible types."
Item 46: Define non-member functions inside templates when type conversions are desired
Item 47: Use traits classes for information about types
Item 48: Be aware of template metaprogramming
Chapter 8. Customizing new and delete
Item 49: Understand the behavior of the new-handler
Item 50: Understand when it makes sense to replace new and delete
Item 51: Adhere to convention when writing new and delete
Item 52: Write placement delete if you write placement new
Chapter 9. Miscellany
Item 53: Pay attention to compiler warnings.
Item 54: Familiarize yourself with the standard library, including TR1
Item.55: Familiarize yourself with Boost.
Appendix A. Beyond Effective C++
Appendix B. Item Mappings Between Second and Third Editions
Index
index_SYMBOL
index_A
index_B
index_C
index_D
index_E
index_F
index_G
index_H
index_I
index_J
index_K
index_L
index_M
index_N
index_O
index_P
index_R
index_S
index_T
index_U
index_V
index_W
index_X
index_Z

Item 43: Know how to access names in templatized base classes

Suppose we need to write an application that can send messages to several different companies. Messages can be sent in either encrypted or cleartext (unencrypted) form. If we have enough information during compilation to determine which messages will go to which companies, we can employ a template-based solution:


class CompanyA {

public:

  ...

  void sendCleartext(const std::string& msg);

  void sendEncrypted(const std::string& msg);

  ...

};



class CompanyB {

public:

  ...

  void sendCleartext(const std::string& msg);

  void sendEncrypted(const std::string& msg);

  ...

};

...                                     // classes for other companies



class MsgInfo { ... };                  // class for holding information

                                        // used to create a message

template<typename Company>

class MsgSender {

public:

  ...                                   // ctors, dtor, etc.



  void sendClear(const MsgInfo& info)

  {

    std::string msg;

    create msg from info;



    Company c;

    c.sendCleartext(msg);

  }



  void sendSecret(const MsgInfo& info)   // similar to sendClear, except

  { ... }                                // calls c.sendEncrypted

};


This will work fine, but suppose we sometimes want to log some information each time we send a message. A derived class can easily add that capability, and this seems like a reasonable way to do it:


template<typename Company>

class LoggingMsgSender: public MsgSender<Company> {

public:

  ...                                    // ctors, dtor, etc.

  void sendClearMsg(const MsgInfo& info)

  {

    write "before sending" info to the log;



    sendClear(info);                     // call base class function;

                                         // this code will not compile!

    write "after sending" info to the log;

  }

  ...



};


Note how the message-sending function in the derived class has a different name (sendClearMsg) from the one in its base class (there, it's called sendClear). That's good design, because it side-steps the issue of hiding inherited names (see Item 33) as well as the problems inherent in redefining an inherited non-virtual function (see Item 36). But the code above won't compile, at least not with conformant compilers. Such compilers will complain that sendClear doesn't exist. We can see that sendClear is in the base class, but compilers won't look for it there. We need to understand why.

The problem is that when compilers encounter the definition for the class template LoggingMsgSender, they don't know what class it inherits from. Sure, it's MsgSender<Company>, but Company is a template parameter, one that won't be known until later (when LoggingMsgSender is instantiated). Without knowing what Company is, there's no way to know what the class MsgSender<Company> looks like. In particular, there's no way to know if it has a sendClear function.

To make the problem concrete, suppose we have a class CompanyZ that insists on encrypted communications:


class CompanyZ {                             // this class offers no

public:                                      // sendCleartext function

  ...

  void sendEncrypted(const std::string& msg);

  ...

};


The general MsgSender template is inappropriate for CompanyZ, because that template offers a sendClear function that makes no sense for CompanyZ objects. To rectify that problem, we can create a specialized version of MsgSender for CompanyZ:


template<>                                 // a total specialization of

class MsgSender<CompanyZ> {                // MsgSender; the same as the

public:                                    // general template, except

  ...                                      // sendCleartext is omitted

  void sendSecret(const MsgInfo& info)

  { ... }

};


Note the "template <>" syntax at the beginning of this class definition. It signifies that this is neither a template nor a standalone class. Rather, it's a specialized version of the MsgSender template to be used when the template argument is CompanyZ. This is known as a total template specialization: the template MsgSender is specialized for the type CompanyZ, and the specialization is total — once the type parameter has been defined to be CompanyZ, no other aspect of the template's parameters can vary.

Given that MsgSender has been specialized for CompanyZ, consider again the derived class LoggingMsgSender:


template<typename Company>

class LoggingMsgSender: public MsgSender<Company> {

public:

  ...

  void sendClearMsg(const MsgInfo& info)

  {

    write "before sending" info to the log;



    sendClear(info);                          // if Company == CompanyZ,

                                              // this function doesn't exist!

    write "after sending" info to the log;

  }

  ...



};


As the comment notes, this code makes no sense when the base class is MsgSender<CompanyZ>, because that class offers no sendClear function. That's why C++ rejects the call: it recognizes that base class templates may be specialized and that such specializations may not offer the same interface as the general template. As a result, it generally refuses to look in templatized base classes for inherited names. In some sense, when we cross from Object-oriented C++ to Template C++ (see Item 1), inheritance stops working.

To restart it, we have to somehow disable C++'s "don't look in templatized base classes" behavior. There are three ways to do this. First, you can preface calls to base class functions with "this->":


template<typename Company>

class LoggingMsgSender: public MsgSender<Company> {

public:



  ...



  void sendClearMsg(const MsgInfo& info)

  {

    write "before sending" info to the log;



    this->sendClear(info);                // okay, assumes that

                                          // sendClear will be inherited

    write "after sending" info to the log;

  }



  ...



};


Second, you can employ a using declaration, a solution that should strike you as familiar if you've read Item 33. That Item explains how using declarations bring hidden base class names into a derived class's scope. We can therefore write sendClearMsg like this:


template<typename Company>

class LoggingMsgSender: public MsgSender<Company> {

public:

  using MsgSender<Company>::sendClear;   // tell compilers to assume

  ...                                    // that sendClear is in the

                                         // base class

  void sendClearMsg(const MsgInfo& info)

  {

    ...

    sendClear(info);                   // okay, assumes that

    ...                                // sendClear will be inherited

  }



  ...

};


(Although a using declaration will work both here and in Item 33, the problems being solved are different. Here, the situation isn't that base class names are hidden by derived class names, it's that compilers don't search base class scopes unless we tell them to.)

A final way to get your code to compile is to explicitly specify that the function being called is in the base class:


template<typename Company>

class LoggingMsgSender: public MsgSender<Company> {

public:

  ...

  void sendClearMsg(const MsgInfo& info)

  {

    ...

    MsgSender<Company>::sendClear(info);      // okay, assumes that

    ...                                       // sendClear will be

  }                                           //inherited



  ...

};


This is generally the least desirable way to solve the problem, because if the function being called is virtual, explicit qualification turns off the virtual binding behavior.

From a name visibility point of view, each of these approaches does the same thing: it promises compilers that any subsequent specializations of the base class template will support the interface offered by the general template. Such a promise is all compilers need when they parse a derived class template like LoggingMsgSender, but if the promise turns out to be unfounded, the truth will emerge during subsequent compilation. For example, if the source code later contains this,


LoggingMsgSender<CompanyZ> zMsgSender;



MsgInfo msgData;



...                                          // put info in msgData



zMsgSender.sendClearMsg(msgData);            // error! won't compile


the call to sendClearMsg won't compile, because at this point, compilers know that the base class is the template specialization MsgSender<CompanyZ>, and they know that class doesn't offer the sendClear function that sendClearMsg is trying to call.

Fundamentally, the issue is whether compilers will diagnose invalid references to base class members sooner (when derived class template definitions are parsed) or later (when those templates are instantiated with specific template arguments). C++'s policy is to prefer early diagnoses, and that's why it assumes it knows nothing about the contents of base classes when those classes are instantiated from templates.

Things to Remember

  • In derived class templates, refer to names in base class templates via a "this->" prefix, via using declarations, or via an explicit base class qualification.