Advertisement Jump to content
Sign in to follow this  
TheComet

Assignment operator & inheritance

This topic is 1887 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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!

Share this post


Link to post
Share on other sites
Advertisement

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

Share this post


Link to post
Share on other sites

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;
}

Share this post


Link to post
Share on other sites

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 );

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites


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?

Share this post


Link to post
Share on other sites


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;
    }
};

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!