Name hiding in inheritance

The C++ name hiding rules for variables are well known by software developers. In contrast, name hiding in inheritance sometimes leads to issues although it follows the same rules. Such issues are therefore not a result of the rules but an effect due to different expectations in these different programming scenarios.

Name hiding rules for variables

Let’s start with the well-known rules for variables. The following example shows a typical scenario.

double x;
	
int main()
{
	int x;

	x = 5.5;	// conversion from double to int

	std::cout << x << std::endl;	// prints 5

	::x = 8.8;	// changes the global x

	std::cout << x << std::endl;	// prints 5
	
	return 0;
}

Within this example you see a double variable within the global scope and an integer variable within the local scope. As they have the same names, the local variable hides the global one, even if they have different types. If we want to access the double variable we must use the global namespace explicitly.

Name hiding rules in inheritance

At next, let us try the same name hiding within an inheritance scenario.

class Base
{
public:
	void Calc(double x) { std::cout << "Base calc was called" << std::endl; }
};

class Derived : public Base
{
public:
	void Calc(int x) { std::cout << "Derived calc was called" << std::endl; }
};

int main()
{
	Derived d;

	d.Calc(3.5);	// derived is called, conversion of double to int
	d.Calc(8);		// derived is called

	return 0;
}

Within this example we have a function with a double parameter in the base class and a function with an integer parameter in the derived class. The same name hiding rules like in the example before are still used. The local function of the derived class will hide the global function of the base class even if the function parameters are different.

Some developers are surprised by this behavior as they expect that the public functions of the base class will become functions of the derived class too and a function call with respect to the function parameters types can be executed. That’s a valid and comprehensible expectation because from a software architectural point of view public inheritance represents a “is-a” relationship.

From technical point of view the different kinds of inheritance just result in different visibilities of interfaces. Therefore, name hiding should be seen with respect to this technical point of view. So, the behavior of the example application is correct.

Make hidden names visible

Of course, there are many use cases where you want to keep the hidden names visible. For example, in public inheritance scenarios you normally want to have the interface visible and it should be a rare case to keep it hidden. As expected, this behavior was respected for C++. With the “using” statement the hidden names will become visible again. The following example shows the according modification of the derived class. This time the base class method is called if we pass a double parameter.

class Base
{
public:
	void Calc(double x) { std::cout << "Base calc was called" << std::endl; }
};

class Derived : public Base
{
public:
	using Base::Calc;	// make base class function name visible in derived class

	void Calc(int x) { std::cout << "Derived calc was called" << std::endl; }
};

int main()
{
	Derived d;

	d.Calc(3.5);	// base is called
	d.Calc(8);		// derived is called

	return 0;
}

Make specific hidden name visible

Within the previous example we have seen the “using” statement to make hidden names visible. As we already learned, names are independent of data types. Therefore, if we have different functions with the same name implemented in the base class, these functions will become visible. The following source code shows an according example. Furthermore, I have changed to private inheritance to show that the concept is independent of the inheritance kind.

class Base
{
public:
	void Calc(double x) { std::cout << "Base calc double was called" << std::endl; }

	void Calc(std::string x) { std::cout << "Base calc string was called" << std::endl; }
};

class Derived : private Base
{
public:
	using Base::Calc;	// make base class function name visible in derived class

	void Calc(int x) { std::cout << "Derived calc was called" << std::endl; }
};

int main()
{
	Derived d;

	d.Calc(3.5);	// base is called
	d.Calc(8);		// derived is called

	d.Calc("abc");

	return 0;
}

Based on a technical point of view the example looks fine. But from a software architectural point of view you may argue that it is bad design if we make the private interface public in derived class. And you are totally right. But sometimes such design decisions are made for several reasons. But in this case you want to keep the design fault as small as possible and make only one or a few of the available functions public. You can do this by using forward declaration instead of the “using” declaration. The following source code shows the adapted example.

class Base
{
public:
	void Calc(double x) { std::cout << "Base calc double was called" << std::endl; }

	void Calc(std::string x) { std::cout << "Base calc string was called" << std::endl; }
};

class Derived : private Base
{
public:
	void Calc(double x) { Base::Calc(x); };

	void Calc(int x) { std::cout << "Derived calc was called" << std::endl; }
};

int main()
{
	Derived d;

	d.Calc(3.5);	// base is called
	d.Calc(8);		// derived is called

	d.Calc("abc");	// compiler error

	return 0;
}

Summary

Names in derived classes hide names of base classes. This behavior is correct from a technical point of view. But in case of public inheritance it contradicts our expectations from a software architectural point of view. But we can easily make the hidden names visible again with the “using” declaration or with forward declarations.

Werbung
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s