Reputation: 490
I run into MISRA C++ 2008 Guidelines, and a rule 12-8-2 in this guideline says:
The copy assignment operator shall be declared protected or private in an abstract class.
and then I thought, when I make an abstract class's assignment operator public,
Is it possible to call it from another class except its subclass?
I think it's impossible.
If this is true, why they define this rule?
Basically, from the point of view of class design, I don't use abstract class which has private member, and I don't define assignment operator in a base class. So, usually, there is no need to apply this rule. However, If there is an abstract base class's public assignment operator, I would make it protected (or private if possible), because it makes no sense to be public. Do you know any other good reasons to apply this rule?
Did I overlook anything?
Upvotes: 4
Views: 2491
Reputation: 6494
The rule does make sense for a couple of reasons:
Suppose we have an abstract base class Animal
and a couple of derived classes, Lizard
and Chicken
:
class Animal {
public:
...
};
class Lizard: public Animal {
...
};
class Chicken: public Animal {
...
};
The assignment operator by default is public. Now consider this code:
Lizard liz1;
Lizard liz2;
Animal *pAnimal1 = &liz1;
Animal *pAnimal2 = &liz2;
...
*pAnimal1 = *pAnimal2;
The assignment operator invoked on the last line is that of the Animal
class, even though the objects involved are of type Lizard. Therefore, only the Animal
part of liz1 will be modified. This is a partial assignment. After the assignment, liz1's Animal
members have the values they got from liz2, but liz1's Lizard
members remain unchanged.
It also possible via Animal
pointers to assign a Lizard
to a Chicken
. That doesn't make a lot of sense, since they are incompatible with each other, and we might want to prevent such a thing happening. The easiest way to prevent such assignments is to make operator=
protected in Animal
. That way, lizards can be assigned to lizards and chickens can be assigned to chickens, but partial and mixed-type assignments are forbidden:
class Animal {
protected:
Animal& operator=(const Animal& rhs);
...
};
Alternatively, the assignment operator can be defined =delete
if copying is to be prohibited in this class hierarchy. Actually, assignment operators that are private or protected needn't return *this
at all. They can return the type void.
Upvotes: 0
Reputation: 115
BЈовић: However, if your class is really abstract (meaning it has pure virtual methods), then the rule makes no sense.
It does, as the assignment operator could accidentaly be called on pointers/references to the base class, which would always lead to slicing.
class cA
{
public:
int x;
virtual ~cA() { }
virtual void f() = 0;
};
class cB: public cA
{
int y;
virtual void f() { }
};
cA * a = new cB, * b = new cB;
*a = *b; // slicing: x is copied, y is not
Upvotes: 0
Reputation: 490
I confirmed slicing problem pointed out by @BЈовић and @NIRAJ RATHI,
then I noticed a case that It's possible to call public assignment operator in abstract base class by using reference. It's possible but possibly causing slicing problem. So making assignment operator virtual, overriding it in subclass and downcasting is needed.
#include <stdio.h>
class X {
public:
int x;
X(int inx): x(inx) {}
virtual void doSomething() = 0;
virtual void print() { printf("X(%d)\n",x); }
virtual X& operator=(const X& rhs) {
printf("X& X::operator=() \n");
x = rhs.x;
return *this;
}
};
class Y : public X {
public:
int y;
Y(int inx,int iny) : X(inx), y(iny) {}
void doSomething() { printf("Hi, I'm Y."); }
virtual void print() { printf("Y(%d,%d)\n",x,y); }
virtual X& operator=(const X& rhs);
};
X& Y::operator=(const X& rhs) {
printf("X& Y::operator=() \n");
const Y& obj = dynamic_cast<const Y&>(rhs);
X::operator=(rhs);
y = obj.y;
return *this;
}
int main()
{
Y a(1,2);
Y a2(3,4);
X& r = a;
r = a2; // calling assignment operator on ABC without slicing!!
r.print();
}
In my conclusion:
- It's possible to call assignment operator in abstract base class by using reference.
- The rule 12-8-2 is intended to prevent slicing problem.
- If there is no slicing problem, I don't always have to apply the rule 12-8-2.
Upvotes: 0
Reputation: 64273
If they consider a class with virtual function (not pure) an abstract, then most likely to prevent slicing. Normal terminology for it is base class.
#include <iostream>
struct A
{
virtual ~A(){}
virtual void foo(){ std::cout<<1<<std::endl; };
};
struct B : A
{
virtual void foo(){ std::cout<<2<<std::endl; };
};
int main()
{
B b;
A a = b; // ops, wrong output because of slicing
}
However, if your class is really abstract (meaning it has pure virtual methods), then the rule makes no sense.
struct A
{
virtual ~A(){}
virtual void foo() = 0;
};
struct B : A
{
virtual void foo(){}
};
int main()
{
B b;
A a = b; // compilation error
}
I don't define assignment operator in a base class.
It doesn't matter whether you define the assignment operator or not. If you do not do it, the compiler will generate one for you.
Upvotes: 2
Reputation: 4016
By definition, abstract classes cannot be instantiated (e.g. see here). A class that cannot be instantiated cannot be copied. Therefore the rule is absurd.
Upvotes: 0
Reputation: 1968
Abstract class are the classes whose implementation is unknown but you know how they will behave or interact with other classes. Hence it is unlikely that you know size or other details about abstract class which is actually needed in copy and assignment operators.
Also main problem comes with something earlier answers to this post talks about "Slicing problem" which becomes more problematic in polymorphic classes as assignment operator is not by default virtual.
Consider this
class A
{
};
class B : public A
{
}
int main()
{
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
}
Now in above case a_ref is initialised to b2 object but in next line when it is assigned to b1 it will be A's operator= is called rather than B's operator= which might change other object b2. You can imagine situation where in class A and B are not empty. Hence it is rule that you either make copy constructor and assignment operator as private not public and in case you make assignment operator public in Abstract class then make it virtual and in every derived implementation check compatibility using dynamic_cast.
Upvotes: 2