You are here:

C++/how to prevent overriding of some functions in derived class

Advertisement


Question
Ralph,

I am trying to write a system of classes, in which I have
one particular functionality in one of the base classes.
I am building up some kind of relation between my library
classes and the user defined external classes.
The users will use my library and inherit from my base
classes. My library base classes has some kind of engine
like behavior, where, some virtual methods are called on
the user objects.
But, the library base classes itself has some methods,
which should be called by the user's programme, but should
not be overridden in the user's classes. If the
user overrrides those methods, then the library functions
will  not be calling the right method on the user object
and it will loose its functionality.
So, I want some kind of compiler error, if the user
writes a code, overriding (some) base class methods.


classes abc and pqr are in a library file.
class userdefinedclass and main are written by user.


class abc
{
  virtual int meth1()
  {
     cout << "this is meth1 of abc" <<endl;
     return(0);
  }
};


class pqr : public abc
{
  virtual int meth1()
  {
     // some critical functionality
     cout << "this is meth1 of pqr" <<endl;
     meth2();
     return(0);
  }
  virtual int meth2()
  {
     // default meth2 provided for user level.
     cout << "this is meth2 of pqr" <<endl;
     return(0);
  }
}


class userdefinedclass :public pqr
{
  // must not be able to override meth1();  

  int meth2()
  {
     // ...
     return(0);
  }


}



int main(int argc, char *argv[])
{
  abc* ptr = new userdefinedclass()
  abc->meth1();


  return(0);
}



Could you please help?

Thanks and Regards,
Jose PC  

Answer
In short you cannot. Once you declare a member function is virtual any sub-class can override it - it does not matter whether it is one you wrote or one someone else wrote.

[
   BTW: Thanks for the example code. However it would be a good
   idea if you compiled it before posting as it contains several
   typos: missing semi-colons, trying to access private member
   functions, abc->meth1() calls on class not object (ptr->meth1()
   is what I presume you meant!), oh, and _no_ virtual destructor
   in abc - OK so it is probably not an issue in these toy examples
   classes but you should be in the habit of providing one to any
   class hierarchy involving virtual member functions.
]

This means we have to put our thinking caps on to arrange things as we wish.

The first obvious thing is to ask: does meth1 actually need to be virtual at all? In this case it would seem so as the internal pqr class overrides it. However you may find cases where it makes less sense, after consideration, for some base class member functions to be virtual. In these cases overriding can easily be prevented by not making such member functions virtual.

OK so what of the other cases where we do want the use to override some member functions and not others? Well one solution is that we split the classes into 2 in an adaptation of the opaque pointer, Cheshire cat or pimpl idiom where the use only sees a wrapper to each class and that wrapper delegates work to an implementation class defined elsewhere, see for example Scott Meyer book "Effective C++" item 34, or many references on the internet (e.g. http://en.wikipedia.org/wiki/Opaque_pointer, http://c2.com/cgi/wiki?PimplIdiom, http://www.octopull.demon.co.uk/arglib/TheGrin.html, http://www.gotw.ca/gotw/024.htm, etc.), and probably elsewhere.

In this case the most direct change we can make to get things working as you wish is to place the virtual meth1 member function into a parallel set of classes that only code internal to the library can see, say ending each class name in _impl. Thus we have abc and abc_impl, pqr and pqr_impl. Just as pqr is derived from abc so pqr_impl is derived from abc_impl.

The abc_impl class defies a _virtual_ meth1 member function:

   class abc_impl
   {
   public:
       virtual int meth1()
       {
         cout << "this is meth1 of abc_impl" <<endl;
         return(0);
       }

       virtual ~abc_impl() {}
   };

The abc class holds a pointer to the required specific impl class. This pointer is set when an abc object is constructed. The default constructor sets it to a newly created abc_impl. A second protected constructor is used by derived classes to set the abc imple class pointer from hteir constructor(s). Finally abc defines a _non_ virtual meth1 member function which can be called by derived classes but not overridden (as it is not virtual!). This non-virtual meth1 member function passes the call onto the impl class instance it holds a pointer to. Note that this class is also responsible for deleting the impl object it holds a pointer to (thus virtual destructors for the impl classes are definitely needed!):

   class abc
   {
   public:
       int meth1()
       {
         pImpl_->meth1();
         return(0);
       }

       abc() : pImpl_( new abc_impl ) {}

       virtual ~abc() { delete pImpl_; }

   protected:
       abc( abc_impl * pImpl ) : pImpl_(pImpl) {}

   private:
       abc_impl *   pImpl_;
   };

Now you have made things interesting in pqr in that its override of meth1 calls the (possibly overridden by a user defined class) meth2. As the overridden meth1 will now be in the companion pqr_impl class this will not work as is - a pqr_impl instance know nothing of its associated pqr object. The solution is to have pqr_impl hold a back reference to its owning pqr object, and call meth2 on this pointer:

   class pqr;

   class pqr_impl : public abc_impl
   {
   public:
       pqr_impl( pqr * pUser ) : pUser_( pUser ) {}

       int meth1();

   private:
       pqr *  pUser_;
   };

Note that pqr has to be declared before a pointer to it can be defined, and that the implementation of pqr_impl::meth1 need to know about the implementation of pqr (to call meth2) so the implementation of of pqr_impl::meth1 has to occur after the definition of pqr.

Note that pqr_impl derives publically from abc_impl and takes a pqr pointer in its constructor, using it to set its pUser_ data member.

The pqr class definition is as follows:

   class pqr : public abc
   {
   public:
       pqr() : abc( new pqr_impl(this) ) {}

       virtual int meth2()
       {
       // default meth2 provided for user level.
         cout << "this is meth2 of pqr" <<endl;
         return(0);
       }
   };

It derives publically from abc as before and passes a newly created pqr_impl object to the protected abc constructor. Notice the pqr_impl object is initialised with the pqr this pointer. Using the this pointer in constructors can cause some compilers to issue warnings, but I have never had any problems with using this pointers in this fashion where they are just stored elsewhere during construction and not used.

The definition of the user-overridable virtual meth2 member function remains the same.

Now pqr is defined we can define the pqr_impl::meth1 function:

   int pqr_impl::meth1()
   {
   // some critical functionality
       cout << "this is meth1 of pqr_impl" <<endl;
       pUser_->meth2();
       return(0);
   }

This is as it was before except that we now have to call pqr::meth2 through the stored pointer to the associated pqr instance. Note that the pqr_impl instance does not own the pqr instance so does not have to worry about deleting it.

Now for a (slightly modified) userdefinedclass definition:

   class userdefinedclass : public pqr
   {
   public:
     // does not be override meth1 but hides abc::meth1
       int meth1()
       {
         cout << "this is meth1 of userdefinedclass" << endl;
         return 1;
       }

       int meth2()
       {
         cout << "this is meth2 of userdefinedclass" << endl;
         return 0;
       }
   };

I have created a meth1 definition for userdefinedclass, and both meth1 and meth2 for userdefinedclass output some text in the style of your other classes. Note the comment: this meth1 member function does _not_ override the meth1 definition in abc as the abc::meth1 is not virtual anymore. Instead it _hides_ the definition in that class.

This is because if we have a userdefinedclass instance and call meth1 the compiler searches for _any_ entity named meth1 starting with the class in question, userdefinedclass, then looking at its base classes. However the C++ compiler will _stop_ looking for anything named meth1 as soon as it finds anything named meth1 - whether it is a suitable match in this instance or not - so if we create a userdefinedclass object and call meth1 on it:

  userdefinedclass udi;
  udi.meth1();

We find that we get the following output:

   this is meth1 of userdefinedclass

Because the meth1 definition in userdefinedclass was located first as a match. However the compiler will still find a meth1 in userdefinedclass and try to use it for the above code it we change the definition of meth1 like so:

       int meth1(int n)
       {
         cout << "this is meth1 of userdefinedclass; n=" << n << endl;
         return 1;
       }

As the revised meth1 definition does not match that of the function call the compiler issues an error, e.g.:

   main.cpp(240) : error C2660: 'userdefinedclass::meth1' :
   function does not take 0 arguments

or even like so:

       int meth1;

We will get an error in this case because the name meth1 resolved to a data member and not a member function:

   main.cpp(240) : error C2064: term does not evaluate to a
   function taking 0 arguments

Note that some compilers (with the correct command line options and flags presumably) will issue a warning if it detects a name hiding a base class name, although the C++ standard does not require any such diagnostic in this case.

So if we now try you original code:

  abc* abc_ptr = new userdefinedclass();
  abc_ptr->meth1();

We get the following output:

   this is meth1 of pqr_impl
   this is meth2 of userdefinedclass

Notice that abc::meth1 is called as we are calling through an abc pointer, and as noted at length above the meth1 definition not being virtual mean the definition in userdefinedclass does not come into play - the classes looked for an item named meth1 in are abc as we are calling through an abc pointer. Thus the abc::meth1 definition is used, which calls the virtual pImpl_ definition, which as this is really an object derived from pqr will be a pqr_impl, so the pqr_impl::meth1 is called, which then calls the virtual pqr::meth2. As userdefinedclass overrides pqr::meth2, this is the version that is called.

All that remains is that you tidy up the actual implementation of this idea. The header(s) that the user uses cannot contain full definitions of the abc_impl and pqr_impl classes. Instead use class declarations:

   // in abc.h

   class abc_impl;

   class abc
   {
   // ...
   };


   // in pqr.h

   class pqr_impl;

   class pqr
   {
   // ...
   };

These are of course publically available to users of your library (i.e. place them in an include directory for user includable headers for your library).

The definitions of abc_impl, and the functions of abc and pqr that rely on a full definitions of abc_impl and/or pqr_impl should go into private source files in the source directory: e.g abc_impl.h, abc.cpp pqr_impl.h and pqr.cpp. The impl class definitions may need to be in separate private internal headers as pqr_impl requires the definition of abc_impl - and any impl class derived from pqr_impl would require the definition of pqr_impl. Thus the layout would need to be something like:

   Public/User/include:
       abc.h
       pqr.h

   Private/source
       abc_impl.h
       pqr_impl.h
       abc.cpp
       pqr.cpp

You can decide whether it is worth having abc_impl.cpp and pqr_impl.cpp or whether their implementation details can be lumped in with those for abc and pqr in abc.cpp and pqr.cpp.

Hope this helps, and maybe now I have engaged your brain you can think of some other ways to do what you wish...  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.