Pergunta

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&)|
Foi útil?

Solução

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.

Outras dicas

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;

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.

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top