Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ swap problem in inheritance scenario

Tags:

c++

boost

swap

I want to add swap functionality to two existing C++ classes. One class inherits from the other. I want each classes' instances to only be swappable with instances of the same class. To make it semi-concrete, say I have classes Foo and Bar. Bar inherits from Foo. I define Foo::swap(Foo&) and Bar::swap(Bar&). Bar::swap delegates to Foo::swap. I want Foo::swap to only work on Foo instances and Bar::swap to only work on Bar instances: I can't figure out how to enforce this requirement.

Here's a sample of what's giving me trouble:

#include <algorithm>
#include <iostream>

struct Foo {
    int x;
    Foo(int x) : x(x) {};

    virtual void swap(Foo &other) {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
        std::swap(this->x, other.x);
    };
};

struct Bar : public Foo {
    int y;
    Bar(int x, int y) : Foo(x), y(y) {};

    virtual void swap(Bar &other) {
        std::cout << __PRETTY_FUNCTION__ << " ";
        Foo::swap(other);
        std::swap(this->y, other.y);
    };
};

void display(Foo &f1, Foo &f2, Bar &b34, Bar &b56)
{
    using namespace std;

    cout << "f1:  " << f1.x                  << endl;
    cout << "f2:  " << f2.x                  << endl;
    cout << "b34: " << b34.x << " " << b34.y << endl;
    cout << "b56: " << b56.x << " " << b56.y << endl;
}

int main(int argc, char **argv)
{
    {
        Foo f1(1), f2(2);
        Bar b34(3,4), b56(5,6);
        std::cout << std::endl << "Initial values: " << std::endl;
        display(f1,f2,b34,b56);
    }

    {
        Foo f1(1), f2(2);
        Bar b34(3,4), b56(5,6);
        std::cout << std::endl << "After Homogeneous Swap: " << std::endl;
        f1.swap(f2);             // Desired
        b34.swap(b56);           // Desired
        display(f1,f2,b34,b56);
    }

    {
        Foo f1(1), f2(2);
        Bar b34(3,4), b56(5,6);
        std::cout << std::endl << "After Heterogeneous Member Swap: " << std::endl;
        // b56.swap(f2);         // Doesn't compile, excellent
        f1.swap(b34);            // Want this to not compile, but unsure how
        display(f1,f2,b34,b56);
    }

    return 0;
}

Here's the output:

Initial values: 
f1:  1
f2:  2
b34: 3 4
b56: 5 6

After Homogeneous Swap: 
virtual void Foo::swap(Foo&)
virtual void Bar::swap(Bar&) virtual void Foo::swap(Foo&)
f1:  2
f2:  1
b34: 5 6
b56: 3 4

After Heterogeneous Member Swap: 
virtual void Foo::swap(Foo&)
f1:  3
f2:  2
b34: 1 4
b56: 5 6

You can see in the final output group where f1.swap(b34) "sliced" b34 in a potentially nasty way. I'd like the guilty line to either not compile or blow up at runtime. Because of the inheritance involved, I think I run into the same problem if I use a nonmember or friend swap implementation.

The code is available at codepad if that helps.

This use case arises because I want to add swap to boost::multi_array and boost::multi_array_ref. multi_array inherits from multi_array_ref. It only makes sense to swap multi_arrays with multi_arrays and multi_array_refs with multi_array_refs.

like image 522
Rhys Ulerich Avatar asked Dec 07 '25 10:12

Rhys Ulerich


1 Answers

Swap, like assignment and comparison work well with value types and don't work well with bases of class hierarchies.

I've always found it easiest to follow the Scott Meyer's Effective C++ recommendation of not deriving from concrete classes and making only leaf classes concrete. You can then safely implement swap, operator==, etc. as non-virtual functions for leaf nodes only.

While it's possible to have a virtual swap functions the whole point of having virtual base classes is to have dynamic behaviour at runtime so I think you're on to a loser trying to get all incorrect possibilities to fail at compile time.

If you want to go the virtual swap route, then one possible approach is to do something like this.

class Base
{
public:
    virtual void Swap(Base& other) = 0;
};

class ConcreteDerived
{
    virtual void Swap(Base& other)
    {
        // might throw bad_cast, in this case desirable
        ConcreteDerived& cother = dynamic_cast<ConcreteDerived&>(other);bad_cast
        PrivateSwap(cother);
    }

    void PrivateSwap(ConcreteDerived& other)
    {
        // swap implementation
    }
};
like image 170
CB Bailey Avatar answered Dec 09 '25 23:12

CB Bailey



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!