copy and swap idiom with pure virtual class
Asked Answered
L

4

7

I am trying to implement virtual class with pure virtual method and 'copy and swap' idiom, but I've encountered some problems. Code won't compile because I am creating instance in the assign operator of the class A which contains pure virtual method.

Is there a way how to use pure virtual method and copy and swap idiom?

class A
{
public:
    A( string name) :
            m_name(name) { m_type = ""; }
    A( const A & rec) :
            m_name(rec.m_name), m_type(rec.m_type) {}
    friend void swap(A & lhs, A & rhs)
    {
        std::swap(lhs.m_name, rhs.m_name);
        std::swap(lhs.m_type, rhs.m_type);
    }

    A & operator=( const A & rhs)
    {
        A tmp(rhs); 
        swap(*this, tmp);
        return *this;
    }

    friend ostream & operator<<( ostream & os,A & x)
    {
         x.print(os);
         return os;
    }

protected:
    virtual void print(ostream & os) =0;    

    string m_type;
    string m_name;
};

class B : A
{
public:
    B(string name, int att) :
        A(name),
        m_att(att)
        {
            m_type="B";
        }

    B( const B & rec) :
        A(rec),
        m_att(rec.m_att) {}

    friend void swap(B & lhs, B & rhs)
    {
        std::swap(lhs.m_att, rhs.m_att);
    }

    B & operator=( const B & rec)
    {
        B tmp(rec) ;
        swap(*this, tmp);
        return *this;
    }

private:
    virtual void print(ostream & os);

    int m_att;

};

Error message:

In member function ‘A& A::operator=(const A&)’:|
error: cannot declare variable ‘tmp’ to be of abstract type ‘A’|
because the following virtual functions are pure within ‘A’:|
virtual void A::print(std::ostream&)|
Lector answered 6/5, 2014 at 14:7 Comment(5)
You can not have A tmp(rhs);. A is an abstract class.Devotional
@MohitJain I think he is well aware of that. That's why he is asking how to work around this.Cosecant
Maybe this answer is also helpfulCosecant
The copy&swap idiom should not be combined with virtual functions/class hierarchies. That leads to slicing (and UB) and slow implementation (because your temporary copy object creates a full virtual function table). Instead, if you want duplication/assignment with a virtual base class, base your implementation on a clone function with covariant return type.Nevski
BTW: The most advantages of copy-and-swap come if you do A & operator=( A rhs) { swap(*this, rhs); return *this; }Dissimilitude
G
4

As your compiler informs you, you cannot create a variable of abstract type. There is no way of dancing around that.

This leaves you three main options:

Stop using pure virtual functions

First, you could just get rid of the pure virtual methods and provide a little stub in each of them that calls std::terminate, which would obviously break compile time detection of whether all (former) pure virtual methods are overridden in all derived classes.

This will cause slicing, since it will only copy the base class and everything that makes out the derived class is lost.

Use a stub class w/o pure virtual functions

Similar to that, you could create a derived class that implements all virtual methods with simple stubs (possibly calling std::terminate), and is used only used as a "instantiatable version of the base class".

The most important part to implement for this class would be a constructor that takes a const reference to the base class, so you can just use it instead of copying the base class. This example also adds a move constructor, because I am a performance fetishist.

This causes the same slicing problem as the first option. This may be your intended result, based on what you are doing.

struct InstantiatableA : public A {
    InstantiatableA(A const& rhs) : A(rhs) { }
    InstantiatableA(A&& rhs) : A(::std::move(rhs)) { }

    void print(ostream&) override { ::std::terminate(); }
};

A& A::operator=(InstantiatableA rhs) {
    using ::std::swap;
    swap(*this, rhs);
    return *this;
}

Note: This is really a variable of type A, although I said it could not be done. The only thing you have to be aware is that the variable of type A lives inside a variable of type InstantiatableA!

Use a copy factory

Finally, you can add a virtual A* copy() = 0; to the base class. Your derived class B will then have to implement it as A* copy() override { return new B(*this); }. The reason dynamic memory is necessary is because your derived types may require arbitrarily more memory than your base class.

Gratuity answered 6/5, 2014 at 14:22 Comment(0)
S
1

You're just facing the fact that inheritance works awkwardly with copy semantics.

For instance, imagine you found a trick to pass the compiling phase, what would mean (following example uses assignment but the issue is the same with a copy) :

// class A
// a class B : public A
// another class C : public A inheriting publicly from A
// another class D : public B inheriting publicly from B
B b1;
C c1;
D d1;

// Which semantic for following valid construction when copy/assignment is defined in A ?
b1 = c1;
b1 = d1;

A &ra = b1;
B b2;

// Which semantic for following valid construction when copy/assignment is defined in A ?
ra = b2;
ra = c1;
ra = d1;
Shock answered 6/5, 2014 at 14:38 Comment(3)
This doesn't really answer the OP's question.Cosecant
@Cosecant : Well, I think I do. This is because the question, to my mind, can't be answered correctly without creating a big design problem. So it answers the question by saying that the initial issue is in the wish to implement a copy and swap for an entity type.Shock
I see, well you might consider editing this into your answer to make it clearer.Cosecant
A
1

CRTP is a choice:

template<typename swappable>
struct copy_swap_crtp{
    auto& operator=(copy_swap_crtp const& rhs){
         if (this==std::addressof(tmp))
            return *this;
         swappable tmp{rhs.self()};
         self().swap(tmp);
         return *this;
    };
    auto& operator=(copy_swap_crtp && rhs){
         self().swap(rhs.self());
         return *this;
    };
protected:
    auto& self(){return *static_cast<swappable*>(this);};
    //auto& self()const{return *static_cast<swappable const*>(this);};
};

user class:

struct copy_swap_class
: copy_swap_crtp<copy_swap_class>
{
    copy_swap_class(copy_swap_class const&);
    void swap(copy_swap_class&);
};

cheers, FM.

Adversity answered 12/11, 2020 at 16:53 Comment(0)
T
-1

The compiler is right. The class A is abstract class, therefore you can not create instances of it in the operator=.

In B, you just declared the print function, but you didn't implement it. Meaning, you will get linking errors.

By implementing it, it compiles fine (if we ignore various warnings) :

void B::print(ostream & os )
{
  os << m_att;
}

by the way :

  • B inherits privately from A, is that what you wanted?
  • order of initialization in A's copy constructor is wrong
  • you initialized m_type in A's constructor's body and not in the initialization list
Thermometer answered 6/5, 2014 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.