What is Shadowing, and Is It True that C++ Does It?

Here's a little Object-Oriented question: If I have a function f in my base class, say that takes an integer, and I write an overload of that function in a derived class that takes something different, can I still call the original function that was inherited from the base class? The answer might surprise you.

A Simple Base Class

Let's start with some plain vanilla C++, no CLR in sight. Here's a base class:

class A
{
protected:
   int i;
   char* s;
public:
   A():s(NULL),i(-1) {}
   void f(int ii) {i=ii;s=new char(0);}
   void f(char* ss) {i=0; if (s) delete s; s=
        new char[strlen(ss)];strcpy(s,ss);}
   void report () { cout << i << " " << s << endl;}
};

While it isn't likely to win any contest for usefulness, and the second overload of f is a bit long for inlining, it will do. Clearly, you can use this class like this:

A a;
a.f(1);
a.report();
a.f("Hello");
a.report();

Deriving and Overloading

What happens to these overloads of f when I write a base class, B?

class B: public A
{
public:
    void nothing() {;}
    void f(int ii, char* ss){i=ii; if (s) delete s; s=
         new char[strlen(ss)];strcpy(s,ss);}
};

The answer, and this surprises many people, is that they seem to disappear. Here's some calling code:

B b;
b.nothing();
b.f(2);
b.report();
b.f("Yoo-Hoo!");
b.report();

This code compiles only when B::f(int, char*) is commented out. With that overload in place, this code produces messages such as 'B::f' : function does not take 1 arguments. You can call only the two-parameter version of f():

b.f(3,"Oops");
b.report();

Of course, if you absolutely need to get to the base class function, you can, but you have to be explicit about it:

b.A::f(4);
b.report();

Managed Classes

What happens if A and B become managed classes in a managed console application? (For simplicity, let's keep working with int and char*, but I will replace the iostream with System::Console equivalents.)

using namespace System;
__gc class A
{
protected:
   int i;
   char* s;
public:
   A():s(NULL),i(-1) {}
   void f(int ii) {i=ii;s=new char(0);}
   void f(char* ss) {i=0; if (s) delete s; s=
        new char[strlen(ss)];strcpy(s,ss);}
   void report () { Console::Write(__box(i));
                    Console::Write(" ");
                    Console::WriteLine(s);}
};

__gc class B: public A
{
public:
   void nothing() {;}
   void f(int ii, char* ss){i=ii; if (s) delete s; s=
        new char[strlen(ss)];strcpy(s,ss);}
};

What is Shadowing, and Is It True that C++ Does It?

Using these classes requires heap instances and -> instead of .:

   A* a = new A();
   a->f(1);
   a->report();
   a->f("Hello");
   a->report();
   B* b = new B();
   b->nothing();
   b->f(2);
   b->report();
   b->f("Yoo-Hoo!");
   b->report();
   b->f(3,"Oops");
   b->report();
   b->A::f(4);
   b->report();

This main won't compile, just as in the unmanaged case, because the two-parameter overload of f() hides the original one-parameter versions. If you comment out the offending lines, you get exactly the same results as in the unmanaged code.

Is This on Purpose?

So now that you've seen this phenomenon in action, how about giving it a name? Actually, it has two. This behavior is often referred to as "hide-by-name" and it's the way C++ is designed to work. It's also referred to as shadowing. It's a language design decision meant to protect you from base class changes. Consider this pair of classes:

class A
{
protected:
   int i;
public:
   void f(int ii);
};
class B: public A
{
public:
   void f(long ii, char* ss);
};

Imagine that you wrote B, you didn't write A, and that these classes live in some sort of distributed world where someone (a library author?) might add functionality to A without your realizing it. You've written your overload to take a long "just in case," but you actually pass ints to it all the time:

char* s = "Hello";
int i=3;
b.f(i,s);

Then, one day, someone adds an overload to the base class that takes an int and a char*. That's a more precise match, so should the compiler suddenly start calling the new base class overload for you? With hide-by-name, all the base class overloads "disappear" when you write any overload in the derived class, guaranteeing that only overloads you wrote will be called—unless you ask for them specifically by using the scope resolution operator (::) as I did earlier with b.A::f(4).

This behavior in C++ makes sense, but it worries some people who feel it violates substitutability. If you're using inheritance properly, your derived classes should meet the IS A test. Everything you can do with a BankAccount you should also be able to do with a SavingsAccount or a RetirementAccount. Hide-by-name doesn't violate this rule, as long as you've cast your derived class pointer or reference to a base class pointer or reference. In code that knows this is a derived object, the derived methods shadow the base methods.

Does C# Shadow? Does VB?

You may wonder whether other object-oriented languages implement hide-by-name like this. Here are those same two classes in C#:

public class A
{
   protected int i;
   protected String s;

   public A() {s=null;i=-1;}
   public void f(int ii) {i=ii;s="";}
   public void f(String ss) {i=0; s=ss;}
   public void report () { Console.WriteLine("{0} {1}", i,s);}
}

public class B:  A
{
   public void nothing() {;}
   public void f(int ii, String ss){i=ii; s=ss;}
}

And here's a main that uses them:

A a = new A();
a.f(1);
a.report();
a.f("Hello");
a.report();
B b = new B();
b.nothing();
b.f(2);
b.report();
b.f("Yoo-Hoo!");
b.report();
b.f(3,"Oops");
b.report();

So, here's something weird: This code compiles just fine and runs just fine. C# doesn't use hide-by-name; it uses hide-by-signature. Is that a positive, or a negative? The answer probably varies from project to project. Sometimes you want your derived class protected against changes in the base class; other times you might like still being able to call all the base class methods even though you've added some overloads in the derived class. (In C#, you can use the keyword new to get shadowing, and, in VB.NET, you should use either Overloads or Shadows to tell the compiler what you want. Shadows is the default.)

Where Does this Leave the C++ Programmer?

What should you do if you're in C++? If you want shadowing, do nothing. If you want to be able to call some methods from the base class, write explicit wrappers for them:

   void f(int ii) {A::f(ii);}
   void f(char* ss) {A::f(s);}

This makes your design obvious and undoes the shadowing. This leaves control, as usual for C++, in the hands of the programmer. Now that you know what the default behavior is, you can step up and change it if you want. And, you can add another tally to the "differences between C++ and C#" list, too.

About the Author

Kate Gregory is a founding partner of Gregory Consulting Limited (www.gregcons.com). In January 2002, she was appointed MSDN Regional Director for Toronto, Canada. Her experience with C++ stretches back to before Visual C++ existed. She is a well-known speaker and lecturer at colleges and Microsoft events on subjects such as .NET, Visual Studio, XML, UML, C++, Java, and the Internet. Kate and her colleagues at Gregory Consulting specialize in combining software develoment with Web site development to create active sites. They build quality custom and off-the-shelf software components for Web pages and other applications. Kate is the author of numerous books for Que, including Special Edition Using Visual C++ .NET.




Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • Live Event Date: November 13, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT APIs can be a great source of competitive advantage. The practice of exposing backend services as APIs has become pervasive, however their use varies widely across companies and industries. Some companies leverage APIs to create internal, operational and development efficiencies, while others use them to drive ancillary revenue channels. Many companies successfully support both public and private programs from the same API by varying levels …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds