Why can't have I a pure virtual assignment operator?
Asked Answered
L

3

6

I'm a bit lost in C++ operators. I'd like to enforce the assignment operator for two different classes, i.e. so one can assign one to each other:

class A {
public:
    virtual A &operator =(const A &a) = 0;
};

class B : public A {
public:
    virtual A &operator =(const A &a) override {
        std::cout << "B" << std::endl;
        return *this;
    }
};

class C : public A {
public:
    virtual A &operator =(const A &a) override {
        std::cout << "C" << std::endl;
        return *this;
    }
};

int main(int argc, char *argv[])
{
    B b;
    C c;
    b = c;

    // leads to a linker error: undefined reference to `A::operator=(A const&)'
    //B b2;
    //b = b2;
}

The first assignment seems to do the job, "B" is called. Similarly, for "c = b", "C" is called. However when I uncomment the second part, I get the linker error. If I define A's operator like:

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

I get "B", "A". Huh? Can somebody explain why "A" is needed when two B's are assigned but not when B <- C is?

Lait answered 6/7, 2017 at 7:53 Comment(2)
You do realize that the compiler also creates a default copy constructor for B and C, as well as default NON VIRTUAL B::operator=(const B&) and C::operator(const C&) operators.... Creating virtual (or even non-virtual) assignment operators from base to derived is asking for trouble.Intestinal
This isn't the problem, but do you really need the extra stuff that std::endl does? '\n' ends a line.Cult
S
5

The compiler generates an implicit copy-assignment operator that is being selected when you do a B = B assignment. That is not selected when you do a B = C assignment.

http://en.cppreference.com/w/cpp/language/copy_assignment

https://wandbox.org/permlink/CM5tQU656rnwtrKl

If you look at your error message:

/tmp/cctHhd0D.o: In function `B::operator=(B const&)':
prog.cc:(.text._ZN1BaSERKS_[_ZN1BaSERKS_]+0x1f): undefined reference to `A::operator=(A const&)'
collect2: error: ld returned 1 exit status

You can see that the linker error is from inside B::operator=(B const&), which, since you didn't define one, means it must be auto-generated.

Seasoning answered 6/7, 2017 at 8:1 Comment(3)
So there isn't any straightforward way to force the programmer to provide the assignment operator for given type? I guess the linker error could be called an enforcement but I'd rather prefer a compiler error.Lait
@MiroKropacek as far as I know, no. The copy assignment operator is special. Also, think very carefully about whether it makes sense to do an assignment from a base class to a derived class..Seasoning
tbh, I don't feel very comfortable with it either. It just seemed like the lesser evil. Having separate operator for 'C' in B and for 'B' in C looks even stranger, not to mention the enforcement, having that in A as pure virtual methods just doesn't seem right at all.Lait
M
2

When you assign b = b2;, it tries to call B's default (implicit) assignment. And the default assignment will invoke base class's default assignment, so it will eventually call A::operator=(const A &a), which is pure virtual.

And so you get the link error.

Motoring answered 6/7, 2017 at 8:4 Comment(0)
P
2

According to standard overriding virtual assignment operator of base class in derived class does not prevent generation of default copy assignment operator which is being invoked in your case. This default copy assignment operator of class B will directly call copy assignment operator of class A hence you are getting undefined reference error.

13.5.3 Assignment [over.ass]

2 Any assignment operator, even the copy and move assignment operators, can be virtual. [Note: For a derived class D with a base class B for which a virtual copy/move assignment has been declared, the copy/move assignment operator in D does not override B’s virtual copy/move assignment operator. [Example:

struct B {
    virtual int operator= (int);
    virtual B& operator= (const B&);
};

struct D : B {
    virtual int operator= (int);
    virtual D& operator= (const B&);
};

D dobj1;
D dobj2;
B* bptr = &dobj1;

void f()
{
    bptr->operator=(99); // calls D::operator=(int)
    *bptr = 99; // ditto
    bptr->operator=(dobj2); // calls D::operator=(const B&)
    *bptr = dobj2; // ditto
    dobj1 = dobj2; // calls implicitly-declared D::operator=(const D&)
}
Pleiades answered 6/7, 2017 at 8:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.