Thread: copy assignment of polymorphic classes

  1. #1
    Optics with a Twist skewray's Avatar
    Join Date
    Dec 2006
    Location
    South Pasadena, CA
    Posts
    36

    copy assignment of polymorphic classes

    I have noticed that when using the default copy assignment for a virtual polymorphic class, I do not get the behaviour I expect. Although copy assignment of the derived class works fine, copy assignment of the parent class does not. See below to a sketch of an example. Why is there a difference?

    Code:
    struct trace
        {
        virtual trace* copytype() = 0 ;
        ...
        } ;
    
    struct intersection: public trace	// has no copy assignment operator
        {
        Polynomial<long double> P ;		// has copy assignment operator
        ...
        } ;
    
        ...
        trace* T = this ;
        trace* X = dynamic_cast<intersection*>( T->copytype() ) ;
        ...
        // below works - X.P is copied to T.P
        *this = *( dynamic_cast<intersection*>(X) ) ;
        // below fails - X.P is not properly copied to T.P
        *T = *X ;

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    You have observed type slicing: the copy assignment happens between two trace objects (even though they are actually intersection objects, i.e., the intersection part has been "sliced off" since you dereferenced the pointers to trace), hence only the trace subobjects of the intersection objects are copied.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Aug 2010
    Location
    Poland
    Posts
    733
    Quote Originally Posted by skewray View Post
    I have noticed that when using the default copy assignment for a virtual polymorphic class, I do not get the behaviour I expect. Although copy assignment of the derived class works fine, copy assignment of the parent class does not. See below to a sketch of an example. Why is there a difference?

    Code:
    struct trace
        {
        virtual trace* copytype() = 0 ;
        ...
        } ;
    
    struct intersection: public trace    // has no copy assignment operator
        {
        Polynomial<long double> P ;        // has copy assignment operator
        ...
        } ;
    
        ...
        trace* T = this ;
        trace* X = dynamic_cast<intersection*>( T->copytype() ) ;
        ...
        // below works - X.P is copied to T.P
        *this = *( dynamic_cast<intersection*>(X) ) ;
        // below fails - X.P is not properly copied to T.P
        *T = *X ;
    You'd better use smart pointers, unless you really need covariance in your copytype() function.

    I don't know what you exactly want to do, but if I were you, I would think twice before using polymorphism and copy-assignment together. Most of the time there is only a need either for value semantics or identity semantics. Whenever I face a problem which looks like solvable via polymorphic value types, I always try to find a simpler solution before I continue to go that way. Why? Well, definitely there are usages for polymorphic value types and I do use them in very special cases. However, the main problem is that polymorphic value types may be hard to maintain in the future if the solution is not well thought out:
    - Who and how can create new value types? Is there a need for factory? Traditionally, value types do not need any factory, because their concrete type is always very well known. On the other hand, there is an interface/base class, so each time you declare a value type you must decide whether you need abstraction or concrete functionality.
    - What can you do with two polymorphic values through their base class/interface? Can you compare them or add one to another? Can the operation be always performed, or may it fail sometimes?

    From my experience I can say that it was easy to get either an overengineered solution, or a solution not overengineered but hard to use. As a result, I am very cautious before incorporating a polymorphic value type. Usually there are other, safer ways of achieving the same results, for example using the strategy pattern.

  4. #4
    Optics with a Twist skewray's Avatar
    Join Date
    Dec 2006
    Location
    South Pasadena, CA
    Posts
    36
    The history of this is that I have code that works fine, except that there is one polymorphic function that has identical code for every subclass. I am trying to refactor and put the code into "trace". Even if I declare "intersection" to have a copy assignment, it won't work? So I need an explicit T.Copy(X) function for each subclass?

    I suppose I could use a template, which might be even uglier.
    Last edited by skewray; 03-19-2016 at 10:30 AM.

  5. #5
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by skewray View Post
    The history of this is that I have code that works fine, except that there is one polymorphic function that has identical code for every subclass. I am trying to refactor and put the code into "trace". So even if I declare "intersection" to have a copy assignment, it won't work?
    The thing is, if trace is base class for intersection. It does not know about trace (nor should it), so how does it make sense to put code that clones an intersection into a trace? Similarly, if you make more subclasses from Trace, they would have their own copy logic. You can always use CRTP to reduce the code:

    Code:
    template<typename T>
    class Base
    {
        public: virtual T Clone() { return T(*static_cast<T*>(this)); }
    };
    
    class Derived: public Base<Derived>
    {
    }
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #6
    Optics with a Twist skewray's Avatar
    Join Date
    Dec 2006
    Location
    South Pasadena, CA
    Posts
    36
    The fundamental problem was that I assumed that when the compiler saw "*T = *X", it would magically know, through polymorphism, that X was an "intersection" and use X's copy semantics. Since the compiler can do this, and I could'nt think of any reason why it shouldn't do this, I moronically assumed that it did do this.

    Does this mean that "*T = *X", as a shallow copy assignment, will leave the "intersection" bits of T untouched? Just curious here...I never plan to again dereference a polymorphic base class for the rest of eternity.

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    *T = *X
    This simply means to the compiler to call decltype(T)::operator =, which, while an operator, is actually also a function. That function is not declared virtual, hence the compiler will always call the base class's assignment operator. You CAN make it a virtual function, and this would allow for polymorphism, but you're still limited to using the same interface as for the base.

    Code:
    class A { public: virtual A& operator = (const A&) { std::cout << "Hello World!\n"; return *this; } };
    class B: public A { public: virtual A& operator = (const A&) { std::cout << "Bye World!\n"; return *this; } };
    
    int main()
    {
    	A a;
    	B b;
    	A& rA = a;
    	A& rBThroughA = b;
    	a = a;
    	rBThroughA = rBThroughA;
    }
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Optics with a Twist skewray's Avatar
    Join Date
    Dec 2006
    Location
    South Pasadena, CA
    Posts
    36
    Would this be considered good coding practise? Seems like a very clever way to make unmaintainable code.

  9. #9
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by skewray
    Does this mean that "*T = *X", as a shallow copy assignment, will leave the "intersection" bits of T untouched?
    As I mentioned in post #2, the issue is that the type of both *T and *X are of the base class, so to the compiler it is a call to the base class copy assignment operator, since the copy assignment operator is not virtual, hence resulting in type slicing. If you do declare it to be virtual as in Elysia's post #7, then note that the derived class' override is not a copy assignment operator for the derived class: it is another assignment operator, one that has a const reference parameter of the base class type. You would presumably need to do a dynamic_cast to check that the actual argument really is a B object before doing the assignment of members, and this could be a Bad Thing if you are doing plenty of such assignments.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  10. #10
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by skewray View Post
    Would this be considered good coding practise? Seems like a very clever way to make unmaintainable code.
    No, I wouldn't consider is good practice. I was simply demonstrating the effect of virtual and why *T = *X didn't work the way you wanted it to.
    Typical good practice with polymorphic classes is to use a Clone() method.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  11. #11
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Elysia
    Typical good practice with polymorphic classes is to use a Clone() method.
    A clone method is does polymorphic copy construction, not copy assignment though.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  12. #12
    Optics with a Twist skewray's Avatar
    Join Date
    Dec 2006
    Location
    South Pasadena, CA
    Posts
    36
    Great. I and my open source project thank you all for your help.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Copy assignment help.
    By darren78 in forum C++ Programming
    Replies: 2
    Last Post: 08-08-2010, 06:21 AM
  2. question about copy constructor and assignment operator
    By sugarfree in forum C++ Programming
    Replies: 8
    Last Post: 04-20-2010, 08:39 AM
  3. Copy Constructors and Assignment Operator overloading.
    By leeor_net in forum C++ Programming
    Replies: 1
    Last Post: 11-09-2009, 10:26 PM
  4. Exception-Safe Copy Assignment
    By George2 in forum C++ Programming
    Replies: 22
    Last Post: 04-02-2008, 05:43 AM
  5. Copy Assignment :: C++
    By kuphryn in forum C++ Programming
    Replies: 3
    Last Post: 03-12-2002, 08:58 AM