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 33: Avoid hiding inherited names

Shakespeare had a thing about names. "What's in a name?" he asked, "A rose by any other name would smell as sweet." The Bard also wrote, "he that filches from me my good name ... makes me poor indeed." Right. Which brings us to inherited names in C++.

The matter actually has nothing to do with inheritance. It has to do with scopes. We all know that in code like this,


int x;                        // global variable



void someFunc()

{

  double x;                   // local variable



  std::cin >> x;              // read a new value for local x

}


the statement reading into x refers to the local variable x instead of the global variable x, because names in inner scopes hide ("shadow") names in outer scopes. We can visualize the scope situation this way:

When compilers are in someFunc's scope and they encounter the name x, they look in the local scope to see if there is something with that name. Because there is, they never examine any other scope. In this case, someFunc's x is of type double and the global x is of type int, but that doesn't matter. C++'s name-hiding rules do just that: hide names. Whether the names correspond to the same or different types is immaterial. In this case, a double named x hides an int named x.

Enter inheritance. We know that when we're inside a derived class member function and we refer to something in a base class (e.g., a member function, a typedef, or a data member), compilers can find what we're referring to because derived classes inherit the things declared in base classes. The way that actually works is that the scope of a derived class is nested inside its base class's scope. For example:


class Base {

private:

  int x;



public:

  virtual void mf1() = 0;

  virtual void mf2();

  void mf3();



  ...

};



class Derived: public Base {

public:

  virtual void mf1();

  void mf4();



  ...

};


This example includes a mix of public and private names as well as names of both data members and member functions. The member functions are pure virtual, simple (impure) virtual, and non-virtual. That's to emphasize that we're talking about names. The example could also have included names of types, e.g., enums, nested classes, and typedefs. The only thing that matters in this discussion is that they're names. What they're names of is irrelevant. The example uses single inheritance, but once you understand what's happening under single inheritance, C++'s behavior under multiple inheritance is easy to anticipate.

Suppose mf4 in the derived class is implemented, in part, like this:


void Derived::mf4()

{



  ...

  mf2();



  ...

}


When compilers see the use of the name mf2 here, they have to figure out what it refers to. They do that by searching scopes for a declaration of something named mf2. First they look in the local scope (that of mf4), but they find no declaration for anything called mf2. They then search the containing scope, that of the class Derived. They still find nothing named mf2, so they move on to the next containing scope, that of the base class. There they find something named mf2, so the search stops. If there were no mf2 in Base, the search would continue, first to the namespace(s) containing Base, if any, and finally to the global scope.

The process I just described is accurate, but it's not a comprehensive description of how names are found in C++. Our goal isn't to know enough about name lookup to write a compiler, however. It's to know enough to avoid unpleasant surprises, and for that task, we already have plenty of information.

Consider the previous example again, except this time let's overload mf1 and mf3, and let's add a version of mf3 to Derived. (As Item 36 explains, Derived's overloading of mf3 — an inherited non-virtual function — makes this design instantly suspicious, but in the interest of understanding name visibility under inheritance, we'll overlook that.)


class Base {

private:

  int x;



public:

  virtual void mf1() = 0;

  virtual void mf1(int);



  virtual void mf2();



  void mf3();

  void mf3(double);

  ...

};



class Derived: public Base {

public:

  virtual void mf1();

  void mf3();

  void mf4();

  ...

};


This code leads to behavior that surprises every C++ programmer the first time they encounter it. The scope-based name hiding rule hasn't changed, so all functions named mf1 and mf3 in the base class are hidden by the functions named mf1 and mf3 in the derived class. From the perspective of name lookup, Base::mf1 and Base::mf3 are no longer inherited by Derived!


Derived d;

int x;



...

d.mf1();                   // fine, calls Derived::mf1

d.mf1(x);                  // error! Derived::mf1 hides Base::mf1

d.mf2();                   // fine, calls Base::mf2



d.mf3();                   // fine, calls Derived::mf3

d.mf3(x);                  // error! Derived::mf3 hides Base::mf3


As you can see, this applies even though the functions in the base and derived classes take different parameter types, and it also applies regardless of whether the functions are virtual or non-virtual. In the same way that, at the beginning of this Item, the double x in the function someFunc hides the int x at global scope, here the function mf3 in Derived hides a Base function named mf3 that has a different type.

The rationale behind this behavior is that it prevents you from accidentally inheriting overloads from distant base classes when you create a new derived class in a library or application framework. Unfortunately, you typically want to inherit the overloads. In fact, if you're using public inheritance and you don't inherit the overloads, you're violating the is-a relationship between base and derived classes that Item 32 explains is fundamental to public inheritance. That being the case, you'll almost always want to override C++'s default hiding of inherited names.

You do it with using declarations:


class Base {

private:

  int x;



public:

  virtual void mf1() = 0;

  virtual void mf1(int);



  virtual void mf2();



  void mf3();

  void mf3(double);

  ...

};



class Derived: public Base {

public:

  using Base::mf1;       // make all things in Base named mf1 and mf3

  using Base::mf3;       // visible (and public) in Derived's scope



  virtual void mf1();

  void mf3();

  void mf4();

  ...

};


Now inheritance will work as expected:


Derived d;

int x;



...



d.mf1();                 // still fine, still calls Derived::mf1

d.mf1(x);                // now okay, calls Base::mf1



d.mf2();                 // still fine, still calls Base::mf2



d.mf3();                 // fine, calls Derived::mf3

d.mf3(x);                // now okay, calls Base::mf3


This means that if you inherit from a base class with overloaded functions and you want to redefine or override only some of them, you need to include a using declaration for each name you'd otherwise be hiding. If you don't, some of the names you'd like to inherit will be hidden.

It's conceivable that you sometimes won't want to inherit all the functions from your base classes. Under public inheritance, this should never be the case, because, again, it violates public inheritance's is-a relationship between base and derived classes. (That's why the using declarations above are in the public part of the derived class: names that are public in a base class should also be public in a publicly derived class.) Under private inheritance (see Item 39), however, it can make sense. For example, suppose Derived privately inherits from Base, and the only version of mf1 that Derived wants to inherit is the one taking no parameters. A using declaration won't do the trick here, because a using declaration makes all inherited functions with a given name visible in the derived class. No, this is a case for a different technique, namely, a simple forwarding function:


class Base {

public:

  virtual void mf1() = 0;

  virtual void mf1(int);



  ...                                    // as before

};



class Derived: private Base {

public:

  virtual void mf1()                   // forwarding function; implicitly

  { Base::mf1(); }                     // inline (see Item

30)

  ...

};



...



Derived d;

int x;



d.mf1();                               // fine, calls Derived::mf1

d.mf1(x);                              // error! Base::mf1() is hidden


Another use for inline forwarding functions is to work around ancient compilers that (incorrectly) don't support using declarations to import inherited names into the scope of a derived class.

That's the whole story on inheritance and name hiding, but when inheritance is combined with templates, an entirely different form of the "inherited names are hidden" issue arises. For all the angle-bracket-demarcated details, see Item 43.

Things to Remember

  • Names in derived classes hide names in base classes. Under public inheritance, this is never desirable.

  • To make hidden names visible again, employ using declarations or forwarding functions.