Reason for C++ Member Function Hiding

Reason for C++ member function hiding

Name lookup works by looking in the current scope for matching names, if nothing is found then it looks in the enclosing scope, if nothing is found it looks in the enclosing scope, etc. until reaching the global namespace.

This isn't specific to classes, you get exactly the same name hiding here:

#include <iostream>

namespace outer
{
void foo(char c) { std::cout << "outer\n"; }

namespace inner
{
void foo(int i) { std::cout << "inner\n"; }

void bar() { foo('c'); }
}
}

int main()
{
outer::inner::bar();
}

Although outer::foo(char) is a better match for the call foo('c') name lookup stops after finding outer::inner::foo(int) (i.e. outer::foo(char) is hidden) and so the program prints inner.

If member function name weren't hidden that would mean name lookup in class scope behaved differently to non-class scope, which would be inconsistent and confusing, and make C++ even harder to learn.

So there's no technical reason the name lookup rules couldn't be changed, but they'd have to be changed for member functions and other types of name lookup, it would make compilers slower because they'd have to continue searching for names even after finding matching names in the current scope. Sensibly, if there's a name in the current scope it's probably the one you wanted. A call in a scope A probably wants to find names in that scope, e.g. if two functions are in the same namespace they're probably related (part of the same module or library) and so if one uses the name of the other it probably means to call the one in the same scope. If that's not what you want then use explicit qualification or a using declaration to tell the compiler the other name should be visible in that scope.

member function hiding free function

The logical reason is Consistency.

  • Suppose as per the suggestion, compiler resolves foo(42) to
    ::foo(int).
  • Now after sometime, if you change X::foo() to X::foo(int) then
    foo(42) will be resolved to X::foo(int). Which is not consistent.

That is the also the reason why derived class function hides base class function when there are similar names.

Such cases can be resolved in 2 ways;

(1) Give fully qualified name (e.g. ::foo(42))

(2) Use using utility; e.g.

void bar()
{
using ::foo;
foo(42);
}

C++: rationale behind hiding rule

It's an hairy question, but apparently the idea is that this hiding feature helps avoiding subtle bugs when making changes to a base class (that could otherwise "steal" calls that before would have been handled by the derived class). Still a change in a base class can influence the result of compilation of derived classes so I don't think I understand 100% this explanation.

I agree that this topic is so frequently discussed that probably the hiding actually increases the amount of "surprises" in C++ programmers.

A detailed discussion about this issue can be found here...

Preventing overriding and/or hiding base class function (C++ 11)

I'd say that, yes, it's bad practice. You introduced polymorphism when you didn't want it, just to prevent name hiding.

But what's so bad about name hiding? Obviously, for whatever reason, the design of Derived desires to hide the function foo in its base class; that is what it does — perhaps it makes its own call to Base::foo then performs some additional activity that is required within Derived.

Trying to subvert that is bad enough; trying to do it by co-opting language features that you don't want is even worse.

If you really need to call the base function, use derived.Base::foo(), but be aware that you are essentially breaking the API of the class Derived, which you should instead use as documented.

Name-hiding static member functions in C++: good practice?

The difference here is that the traits class is not polymorphic, so there will never be any confusion as to which interface is used.

ci_char_traits could of course be implemented with private inheritance, encapsulation or simply deferring to std::char_traits<>. All of these approaches would require more maintenance.

Update:

There has been some discussion in the comments on whether templates are polymorphic. I will elaborate.

The construct std::basic_string<char, ci_char_traits> causes template expansion of the template std::basic_string<class CharT, class Traits, class Allocator>. During this expansion, the following type substitutions are made:

  • CharT is replaced with char
  • Traits is replaced with ci_char_traits
  • Allocator is replaced with std::allocator<char>

Therefore, the class generated by the compiler is:

std::basic_string<char, ci_char_traits, std::allocator<char>>

This is not a polymorphic class.

This class will have a member typedef called std::basic_string<char, ci_char_traits, std::allocator<char>>::traits_type, which will unequivocally be the type ci_char_traits.

Therefore, this template expansion will result in a non-polymorphic class which uses the non-polymorphic class ci_char_traits to determine the behaviour of character-based operations such as comparison.

Which overload of eq and lt will be called by code in class std::basic_string<char, ci_char_traits, std::allocator<char>> is precisely defined, which is why in this case, it is not bad practice to overload these static member functions.

C++: Why member function has priority over global function

Consider what would happen if a global function declared somewhere in your code base (possibly several #include statements away) would trump an class obj member function declared right there in the class itself...

It would mean that, if you want to play it safe, you would have to fully qualify every single call to a member function...

this->foo();

...instead of having to qualify the less likely case of actually referring to the global function.

::foo();

This is called the "concept of least surprise".



Related Topics



Leave a reply



Submit