base class provides an interface that is inherited by derived classes. However, in some cases you need to extend, or even alter, the base class’s interface in a derived class. For example, if you’re overloading a member function of the base class in a derived class or readjusting a member’s access.
How do you overload a member function across a class hierarchy? How do you override the access type of a base class’s member in a derived class?
Use a using-declaration to control a member’s access type and to overload member functions across a class hierarchy.
Demonstrating the Problem
Suppose you’re implementing a file system that uses a class hierarchy like this one:
class File{public: int Open(const string & path); int Read (char * buff, size_t bytes); int Write (char *buff, size_t bytes); //..};class DiskFile: public File{public: int Defragment();//..};class NetworkFile : public DiskFile{//.. };
Class NetworkFile implements a special type of file that can be stored and accessed remotely. At this stage, the designer faces two problems. First, unlike a local disk file, network files require a more rigorous authorization check to ensure that only authorized users may access them. Secondly, because the file may reside on a remote machine, it’s necessary to overload the Open() member function to support additional types of path names, or, wstring names. A naive designer might include an additional overloaded version of this function like this:
class NetworkFile : public DiskFile{public: int Open(const wstring & path);//seemingly overloading //..};
This code compiles okay. However, it doesn’t exactly overload File::Open?instead, it hides it:
string path="\usr\image.bmp";NetworkFile nf;nf.Open(path); //compilation error
Here’s the problem: when a compiler employs the overload resolution algorithm, it stops searching at the first scope in which a viable candidate is found. In this example, it finds NetworkFile::Open(const wstring & ) and the search terminates. However, the argument passed to the function is of type string &. Because of the type mismatch the compilation fails.Name Injection
To fix this problem, you have to explicitly inject the name File::Open into the scope of NetworkFile::Open(). This way, any additional function called Open() that you declare in NetworkFile won’t hide File::Open. To do so, use a using-declaration like this:
class NetworkFile : public DiskFile{public: using File::Open; //inject a name into current scope int Open(const wstring & path); //..};
When you add a using-declaration of this kind, make sure that it appears in the public section of the class. Now you can use both overloaded versions of Open() in the derived class:
string path="\usr\image.bmp";wstring wpath=L"\usr\image.bmp";NetworkFile nf, nf2;nf.Open(path); //OK, File::Open(const string &)nf2.Open(wpath); //OK, NetworkFile::Open(const wstring &)
You may add as many overloaded versions of Open() as you like to NetworkFile:
class NetworkFile : public DiskFile{public: using File:Open; //inject name into current scope int Open(const wstring & path); int Open (const char * path); int Open (const wchar_t * path); //..};
Likewise, any class that inherits from NetworkFile can use the same technique to add even more overloaded functions:
class UnixNetworkFile : public NetworkFile{public: using NetworkFile:Open; //inject all overloaded versions int Open(int descriptor);//..};UnixNetworkFile nf1, nf2, nf3;nf.Open(path);nf.Open(wpath);nf.Open(2); //opens stderr
Controlling Access Type
A using-declaration also enables you to change the access type of a base class’s member in a derived class. Consider a ReadOnly class that enables users to read a file but not to change it. You may need to change the access type of Write() to private. This can be accomplished by adding a using-declaration to the private section of the class:
class ReadOnly: public File{private: //change the access of File::Write using File::Write;};
Consequently, ReadOnly objects cannot invoke this member function:
ReadOnly ro;ro.Write(mybuff);//error: Write() is not accessible
The Buck Stops Here
Each derived class can override a previous access type. For example, if you have a member function f() declared public in class A, and a derived class D which changes the access type of f() to private:
class A{public: int f();protected: int g();};class D: public A{private using A::f(); //OK, f() is now private};
A class derived from D may override the access type of f() once again:
class E : public D{public using A::f(); //OK, f() is public again};
However, you cannot grant a more permissive access type than the one originally specified in the member’s declaration. For example, the member function g() is declared protected in A. Class F that is derived from A can change g()‘s access to private. A class derived from F may subsequently change g()‘s access back to protected. However, none of them can change g()‘s access type to public. This restriction ensures that the using-declaration facility doesn’t violate the fundamental aspects of the C++ object model.