Jump to content

  • Log In with Google      Sign In   
  • Create Account

Assignment operator & inheritance


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
11 replies to this topic

#1 TheComet   Members   -  Reputation: 1613

Like
0Likes
Like

Posted 18 November 2013 - 04:16 AM

Consider the following code:

#include <iostream>

class Base
{
protected:
    Base() {}
    Base( const Base& that ) { *this = that; }
    ~Base() {}
public:
    Base& operator=( const Base& that )
    {
        std::cout << "base assignment" << std::endl;
        return *this;
    }
};

class Derived : public Base
{
public:
    Derived() {}
    Derived( const Derived& that ) : Base( that ) { *this = that; }
    ~Derived() {}
    Derived& operator=( const Base& that )
    {
        std::cout << "derived assignment" << std::endl;
        return *this;
    }
};

int main()
{

    Derived test1, test2;

    std::cout << "derived = derived" << std::endl;
    test1 = test2;
    std::cout << std::endl;

    std::cout << "base = derived" << std::endl;
    *static_cast<Base*>( &test1 ) = test2;
    std::cout << std::endl;

    std::cout << "derived = base" << std::endl;
    test1 = *static_cast<Base*>( &test2 );
    std::cout << std::endl;

    std::cout << "base = base" << std::endl;
    *static_cast<Base*>( &test1 ) = *static_cast<Base*>( &test2 );

    return 0;
}

A few questions:

  1. I noticed that operator overloads aren't overridden like normal methods. For instance, if I were to declare virtual Base& operator=( const Base& that ) in the base class, and try to override that in the derived class with Base& operator=( const Base& that ), then were to assign two derived objects, the base overload is still called rather than the derived overload. However, if I assign a base to derived, derived to base, or base to base, the operator overload in the derived class is called. Can someone explain this behavior?
  2. Taking the example from question 1., if I remove the "virtual" keyword from the base declaration of the overload, it behaves differently. Only in the case of assigning a base object to a derived object is the derived operator called. In all other cases, the base operator is called. Can someone explain this behavior?
  3. Is there a way for both overloads (in the base and derived class) to be called when an assignment occurs?

 

Thanks!


YOUR_OPINION >/dev/null


Sponsor:

#2 wintertime   Members   -  Reputation: 1727

Like
0Likes
Like

Posted 18 November 2013 - 05:51 AM

You should always call the Base class version inside Derived class version (I hope you left it out cause its only a test).

You can define more than one operator= . You may therefore want to add a Derived& Derived::operator=(const Derived&) to have it called when you assign a Derived, because thats a better fit for overload resolution. Theoretically you could even add a Base& Base::operator=(const Derived&) if you forward declare Derived class, but it may not be a good thing to do.

When really getting a Derived but only having a Base reference, you could test if you actually got a Derived using a virtual helper method, to not loose data from attributes of Derived.

Btw., calling with a Base reference to a Derived may do different things than using a Base reference to a Base, depending on virtual methods, and you did not try that case.


Edited by wintertime, 18 November 2013 - 05:54 AM.


#3 Brother Bob   Moderators   -  Reputation: 8251

Like
5Likes
Like

Posted 18 November 2013 - 06:18 AM

Consider the following code:

[...]

A few questions:

  1. I noticed that operator overloads aren't overridden like normal methods. For instance, if I were to declare virtual Base& operator=( const Base& that ) in the base class, and try to override that in the derived class with Base& operator=( const Base& that ), then were to assign two derived objects, the base overload is still called rather than the derived overload. However, if I assign a base to derived, derived to base, or base to base, the operator overload in the derived class is called. Can someone explain this behavior?
  2. Taking the example from question 1., if I remove the "virtual" keyword from the base declaration of the overload, it behaves differently. Only in the case of assigning a base object to a derived object is the derived operator called. In all other cases, the base operator is called. Can someone explain this behavior?
  3. Is there a way for both overloads (in the base and derived class) to be called when an assignment occurs?

 

Thanks!

As far as I understand it, the assignment operator is inherited just like any other function, but there are other hidden things going on: a default generated assignment operator.

 

Since there's no assignment operator in the derived class that takes a derived class, it is automatically generated and will first call the assignment operator for the base class and then the assignment operator for the derived class' members. The call to the base class' assignment is then resolved as any other inherited virtual or non-virtual function call.

 

So let's take the derived=derived and base=derived case for examples, for both virtual and non-virtual base-to-base assignment.

  1. derived=derived, non-virtual: The automatically generated Derived::operator=(Derived &) is called, which in turn calls Base::operator=(Base &). Thus, the output says the base-assignment is called.
  2. derived=derived, virtual: Same as point 1 for calling the assignment for the base class. However, the assignment is base-to-base, and thus the virtual call resolves to Base::operator(Base &), thus the output says the base-assignment is called.
  3. base=derived, non-virtual: The derived object is passed to Base::operator=(Base &), and consequently the output says the base-assignment is called.
  4. base=derived, virtual: Same as point 3, but the virtual call is resolved to Derived::operator=(Base &) since the dynamic type of the Base-class object is a Derived object, and the output says the derived-assignment is called.

It may obvious what's going on if you explicitly implement the assignment operator the compiler is automatically adding for you. Interestingly, it also answers your third question. Add this operator to the derived class:

    Base &operator =(const Derived &that)
    {
        Base::operator=(that);
        return *this;
    }


#4 TheComet   Members   -  Reputation: 1613

Like
0Likes
Like

Posted 18 November 2013 - 07:09 AM

That makes a lot of sense, thanks for clearing things up!

 

So in order to account for every possible way a user could copy this object, the whole thing should look like the following? Note that I declared Base' constructor and destructor protected, as I don't want base objects to be instantiated directly without being derived from.

#include <iostream>

class Base
{
protected:
    Base() {}
    ~Base() {}
public:
    virtual Base& operator=( const Base& that )
    {
        std::cout << "Base: Base& operator=( const Base& that )" << std::endl;
        return *this;
    }
};

class Derived : public Base
{
public:
    Derived() {}
    Derived( const Derived& that ) { *this = that; }
    Derived( const Base& that ) { *this = that; }
    ~Derived() {}
    Derived& operator=( const Base& that )
    {
        std::cout << "Derived: Derived& operator=( const Base& that )" << std::endl;
        Base::operator=(that);
        return *this;
    }
    Base& operator=( const Derived& that )
    {
        std::cout << "Derived: Base& operator=( const Derived& that )" << std::endl;
        Base::operator=(that);
        return *this;
    }
};

int main()
{

    Derived test1;
    Derived test2(test1); // copy constructor derived(derived)
    Derived test3( *static_cast<Base*>(&test1) ); // copy constructor derived(base)
    test2 = test1; // assignment derived = derived
    test2 = *static_cast<Base*>( &test1 ); // assignment derived = base
    *static_cast<Base*>( &test1 ) = test2; // assignment base = derived
    *static_cast<Base*>( &test1 ) = *static_cast<Base*>( &test2 ); // assignment base = base

    return 0;
}

YOUR_OPINION >/dev/null


#5 Brother Bob   Moderators   -  Reputation: 8251

Like
3Likes
Like

Posted 18 November 2013 - 07:39 AM

If you want to cover all cases with assignment between derived-only objects via combinations of base and derived references, then I believe that is what you want to do.

 

As a side note, you don't need to cast via pointers, you can cast reference types also.

static_cast<Base &>( test1 ) = static_cast<Base &>( test2 );


#6 TheComet   Members   -  Reputation: 1613

Like
0Likes
Like

Posted 18 November 2013 - 07:50 AM

Thanks for the help! C++ seems to do a lot of implicit assumptions about your code...


YOUR_OPINION >/dev/null


#7 Álvaro   Crossbones+   -  Reputation: 13368

Like
5Likes
Like

Posted 18 November 2013 - 08:00 AM

Why do you have an assignment operator that assigns to a Derived from a Base? I can't think of a situation where that would make sense. My guess is that you have made some poor design choices that lead you to writing code like this.

 

I don't know if I agree with your statement about C++. If anything, I think C++ makes too few assumptions about your code and lets you shoot yourself in the foot in too many ways. One way to use the language is to stay away from its darker corners by restricting yourself to using only the parts that won't get you in trouble. I advocate using only single public non-virtual inheritance, and use it only to achieve polymorphism. Ideally there should be a function that can construct an instance of any of the subclasses from a description (a string or a configuration object of some sort) and returns it as a std::unique_ptr<Base>. The rest of the program should know nothing about the subclasses. If you are trying to copy one of these things, you'll be copying a smart pointer, not the object itself.



#8 TheComet   Members   -  Reputation: 1613

Like
0Likes
Like

Posted 18 November 2013 - 08:39 AM

I was really just experimenting, as I was unclear of how overloads behaved in polymorphic structures.

 

If you're interested, my question was spawned by the following piece of code I wrote. I'm toying around with various pathfinding algorithms and a way to implement them on an abstract representation of data.

#include <iostream>

template <class DATA>
class NodeBase
{
protected:
    NodeBase() {}
    ~NodeBase() {}
public:
    /* omitted methods for linking/unlinking nodes with each other */
    const DATA& getData() const { return m_Data; }
    void setData( const DATA& data ) { m_Data = data; }
    NodeBase& operator=( const NodeBase& that )
    {
        m_Data = that.m_Data;
        return *this;
    }
private:
    DATA m_Data;
    int* m_A_Member_That_Should_Not_Be_Copied_So_I_Am_Forced_To_Overload_The_Assignment_Operator;
};

// extend functionality with coordinates
template <class COORD, class DATA>
class Node : public NodeBase<DATA>
{
public:
    Node() {}
    ~Node() {}
    const COORD& getCoordinate() const { return m_Coordinate; }
    void setCoordinate( const COORD& coord ) { m_Coordinate = coord; }
private:
    COORD m_Coordinate;
};

// coordinates disabled if first template parameter is specified to be "void"
template <class DATA>
class Node<void, DATA> : public NodeBase<DATA>
{
public:
    Node() {}
    ~Node() {}
};

int main()
{

    Node<void, char> test1, copy1;
    Node<int, char> test2, copy2;

    test1.setData( 'a' );
    // test1.setCoordinate( 3 ); <-- should throw a compiler error
    test2.setData( 'b' );
    test2.setCoordinate( 6 );

    copy1 = test1;
    copy2 = test2;

    std::cout << copy1.getData() << std::endl;
    std::cout << copy2.getData() << std::endl;
    std::cout << copy2.getCoordinate() << std::endl;

    return 0;
}

The derived class doesn't require an explicit assignment operator overload because the implicit one is sufficient. I created this thread because I was wondering what would happen if the derived class "Node" suddenly did require an explicit overload.


YOUR_OPINION >/dev/null


#9 Álvaro   Crossbones+   -  Reputation: 13368

Like
0Likes
Like

Posted 18 November 2013 - 09:20 AM


I created this thread because I was wondering what would happen if the derived class "Node" suddenly did require an explicit overload.

 

If the derived class "Node" did require an explicit overload, it would be for an assignment operator that takes an argument of the derived type, not the base type. Or perhaps I didn't fully understand your situation?



#10 Adam_42   Crossbones+   -  Reputation: 2512

Like
1Likes
Like

Posted 19 November 2013 - 06:29 PM


Note that I declared Base' constructor and destructor protected, as I don't want base objects to be instantiated directly without being derived from.

 

As a side note, the standard way to do that is with pure virtual functions. If there's no other function that needs to be pure then make the destructor pure virtual. A class that people inherit from should generally have a virtual destructor anyway - you can run in to trouble when deleting an instance of the class if it's not.

class Base
{
public:
    virtual ~Base() = 0;

    virtual Base& operator=( const Base& that )
    {
        std::cout << "Base: Base& operator=( const Base& that )" << std::endl;
        return *this;
    }
};


#11 TheComet   Members   -  Reputation: 1613

Like
0Likes
Like

Posted 20 November 2013 - 02:49 AM

If the derived class "Node" did require an explicit overload, it would be for an assignment operator that takes an argument of the derived type, not the base type. Or perhaps I didn't fully understand your situation?

 

No you're right, there are probably not many cases where a derived type is assigned to a base type. I was simply being hypothetical.

 

As a side note, the standard way to do that is with pure virtual functions. If there's no other function that needs to be pure then make the destructor pure virtual. A class that people inherit from should generally have a virtual destructor anyway - you can run in to trouble when deleting an instance of the class if it's not.

 

I didn't know you could make the destructor pure virtual, thanks for the tip!


YOUR_OPINION >/dev/null


#12 Hodgman   Moderators   -  Reputation: 30424

Like
3Likes
Like

Posted 20 November 2013 - 03:04 AM

Most of the time, I wouldn't thing that assignment would make sense with polymorphic types.

If B and C are implementations of the A interface, what does it mean to ask a B object to take on the values from a C object? They've got different internal representations, so can't be assigned...

I'd recommend looking into the noncopyable class (or other ways of disabling assignment) -- many polymorphic types are probably naturally non-copyable.
I actually make pretty much all of my classes non-copyable to begin with, until I actually need to be able to clone them ;)




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS