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

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Live Event Date: October 23, 2014 @ 12:00 p.m. ET / 9:00 a.m. PT Despite the current "virtualize everything" mentality, there are advantages to utilizing physical hardware for certain tasks. This is especially true for backups. In many cases, it is clearly in an organization's best interest to make use of physical, purpose-built backup appliances rather than relying on virtual backup software (VBA - Virtual Backup Appliances). Join us for this eSeminar to learn why physical appliances are preferable to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds