You are here:

C++/Alternative to Virtual Clone and Slicing

Advertisement


Question
I've read that the virtual clone method is a solution to the slicing problem. No problem of course with that. But when I first heard of the problem, the first thing that came to mind was to either 1) prevent slicing by making the copy constructor and assignment operator private, -or-, if you need to copy objects  2) make the assignment operator virtual and define it, similar to the virtual clone method, to prevent the slicing.
Please comment.
Thanks.

Answer
Well a clone virtual member function - to use C++ terms - will allow you to create a new copy of the actual type of an object when you only have a reference (C++ pointer or reference type) to its base type.

As you have to call this member function explicitly it will _not_ prevent unintentional slicing as in the following case:

   void f( Base b )
   {
   // ...
   }

   // ...

   Derived d;
   f(d);

(assume Derived is a sub-class of Base and that they are copy constructible)

The object d will be sliced to a new copied Base when passed _by value_ to f(), effectively slicing the Derived d object to a Base object.

In the case of 1) in your question - and this is quite a reasonable thing to do for many types in class hierarchies - the compiler will of course whinge if Base is non-copyable, it having private and undefined copy constructor, and presumably similarly for the assignment operator. This technique, with a private and undefined assignment operator, of course also catches:

   Base b;
   Derived d;
   b = d;

Which of course is not helped at all by the clone technique.

As to 2) in your question I presume you wish to do something like:

   Derived d;
   Derived d2;
   Base * pb(&d);
   *pb = d2;

And have the assignment operator for Derived be called for pb->operator=( d2 );

Yes this can be made to work BUT the form of the virtual Base::operator= will be:

   Base & Base::operator=( Base const & rhs ) { ... }  // declared virtual

And this means that _all_ derived classes that override this base assignment operator must provide an assignment operator of a similar form. So rather than using the usual:

   Derived & Derived::operator=( Derived const & rhs ) { ... }

You would have to use:

   Derived & Derived::operator=( Base const & rhs ) { ... }

[ Note:
       The return type can vary from the Base & declared for Base::operator= (as
       Derived is derived from Base) using a feature called covariant return types - if,
       that is, your compiler supports it. Some, especially older compilers, may not.
       If your compiler does not support covariant return types then you would have to
       use a Derived::operator= like so:

         Base & Derived::operator=( Base const & rhs ) { ... }

-- end note ]

Both forms (and others if needed) can of course be provided as assignment operator functions can be overloaded like other functions.

This means the derived classes' overridden operator= member functions receive a reference to a _base_ object and not a derived object. You therefore need to perform a downcast to obtain a reference to an actual derived object. As there is no way to know that the base object reference you have been handed actually does refer to the required derived type the only safe cast you can perform is a dynamic_cast which is performed at runtime and will throw a std::bad_cast exception if the object referred to is not in fact the expected derived type. If you do not see this consider that the following will all call the Derived operator= (assuming Derived and Derived2 are both publicly derived from Base and all are suitably defined):

   Derived d;
   Base b;
   Base * pb(&d);
   *pb = b;        // Trying to assign a Base to a Derived

   Derived2 d2;
   *pb = d2;       // Trying to assign a Derived2 to a Derived

So effectively what you loose is compile time type checking and you have to provide this at runtime - you have to ensure you catch and handle all those potential std::bad_cast exceptions.

The other option would be to use static_cast to perform the downcasts but given the ease with which such a scheme can be abused, even if you are careful, it will come back and bite you (or someone else) on the behind - probably badly when you are least expecting it. Assuming an X is a Y is a recipe for weird behaviour and long debugging sessions as memory will seemingly be 'randomly' overwritten, or seemingly spuriously acquire rubbish values etc.

Here is a simple example program having Base, Derived and Derived2 classes and a main that performs some assignments that you can play with:

   #include <iostream>  // for std::cerr, std::cout etc.
   #include <typeinfo>  // for std::bad_cast

   class Base
   {
       int state_;

   public:
       Base() : state_(0) {}
       Base( Base const & other ) : state_(other.state_) {}
       virtual ~Base() {}
       
       virtual Base & operator=(Base const & rhs);
   };

   Base & Base::operator=( Base const & rhs )
   {
     this->state_ = rhs.state_;
     return *this;
   }

   class Derived : public Base
   {
       int more_state_;

   public:
       Derived() : more_state_(0) {}
       Derived( Derived const & other )
       : Base( other )
       , more_state_(other.more_state_)
       {}
       
       ~Derived() {}
       
       Derived & operator=(Derived const & rhs);
       Derived & operator=( Base const & rhs );
   };

   Derived & Derived::operator=( Derived const & rhs )
   {
       Base::operator=( rhs );
       this->more_state_ = rhs.more_state_;
       return *this;
   }

   Derived & Derived::operator=( Base const & rhs )
   {
       Derived const & d_rhs( dynamic_cast<Derived const &>(rhs) );
       Base::operator=( rhs );
       this->more_state_ = d_rhs.more_state_;
       return *this;
   }

   class Derived2 : public Base
   {
       double more_state_;

   public:
       Derived2() : more_state_(0.0) {}
       Derived2( Derived2 const & other )
       : Base( other )
       , more_state_(other.more_state_)
       {}
     
       ~Derived2() {}
       
       Derived2 & operator=(Derived2 const & rhs);
       Derived2 & operator=( Base const & rhs );
   };

   Derived2 & Derived2::operator=( Derived2 const & rhs )
   {
       Base::operator=( rhs );
       this->more_state_ = rhs.more_state_;
       return *this;
   }

   Derived2 & Derived2::operator=( Base const & rhs )
   {
       Derived2 const & d_rhs( dynamic_cast<Derived2 const &>(rhs) );
       Base::operator=( rhs );
       this->more_state_ = d_rhs.more_state_;
       return *this;
   }

   int main()
   {
       Derived target;
       Base * pTarget(&target);

       Derived source_d;
       
   // Call Derived::operator=( Derived const & rhs ) non-virtually
       target = source_d;
       
   // Call Derived::operator=( Base const & rhs ) virtually with various RHS types
       std::cout << "Assigning Derived source_d to Derived target...";
       try
       {
         *pTarget = source_d;
         std::cout << "OK\n";
       }
       catch ( std::bad_cast & )
       {
         std::cerr << "FAILED: right hand side is NOT of expected type." << std::endl;
       }

       Base source_b;
       std::cout << "Assigning Base source_b to Derived target...";
       try
       {
         *pTarget = source_b;
         std::cout << "OK\n";
       }
       catch ( std::bad_cast & )
       {
         std::cerr << "FAILED: right hand side is NOT of expected type." << std::endl;
       }

       Derived2 source_d2;
       std::cout << "Assigning Derived2 source_d2 to Derived target...";
       try
       {
         *pTarget = source_d2;
         std::cout << "OK\n";
       }
       catch ( std::bad_cast & )
       {
         std::cerr << "FAILED: right hand side is NOT of expected type." << std::endl;
       }
   }

Have fun.

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.