我可以从子类以外的另一个类调用抽象基类的公共赋值运算符吗?
Can I call abstract base class's public assignment operator from another class except subclass?
我遇到了MISRA C++ 2008年指南,本指南中的规则12-8-2说:
复制赋值运算符应在抽象类中声明为受保护或私有。
然后我想,当我公开抽象类的赋值运算符时,
是否可以从除其子类之外的另一个类调用它?
我认为这是不可能的。
如果这是真的,他们为什么要定义这个规则?
基本上,从类设计的角度来看,我不使用具有私有成员的抽象类,也不在基类中定义赋值运算符。因此,通常无需应用此规则。但是,如果存在抽象基类的公共赋值运算符,我会使其受到保护(如果可能的话,是私有的),因为公共是没有意义的。您知道应用此规则的其他充分理由吗?
我忽略了什么吗?
如果他们认为具有虚函数(非纯函数)的类是抽象的,那么最有可能防止切片。它的正常术语是基类。
#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
}
但是,如果你的类真的很抽象(意味着它有纯虚方法),那么这个规则就没有意义了。
struct A
{
virtual ~A(){}
virtual void foo() = 0;
};
struct B : A
{
virtual void foo(){}
};
int main()
{
B b;
A a = b; // compilation error
}
我不在基类中定义赋值运算符。
是否定义赋值运算符并不重要。如果您不这样做,编译器将为您生成一个。
抽象类是实现未知但你知道它们将如何表现或与其他类交互的类。 因此,您不太可能知道复制和赋值运算符中实际需要的抽象类的大小或其他细节。
主要问题还来自这篇文章的早期答案,讨论"切片问题",这在多态类中变得更加成问题,因为赋值运算符默认情况下不是虚拟的。
考虑一下
class A
{
};
class B : public A
{
}
int main()
{
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
}
现在在上面的情况下,a_ref 被初始化为 b2 对象,但在下一行中,当它分配给 b1 时,它将调用 A 的运算符 = 而不是 B 的运算符 =,这可能会改变其他对象 b2。 您可以想象在类 A 和 B 中不为空的情况。因此,规则是将复制构造函数和赋值运算符设置为私有而不是公共,如果您在抽象类中使赋值运算符公开,则使其成为虚拟,并在每个派生实现中使用dynamic_cast检查兼容性。
根据定义,抽象类不能被实例化(例如,见这里)。 无法实例化的类无法复制。 因此,这个规则是荒谬的。
我确认了@B Јовић和@NIRAJ RATHI指出的切片问题,
然后我注意到一种情况,即可以使用引用在抽象基类中调用公共赋值运算符。这是可能的,但可能会导致切片问题。因此,需要使赋值运算符成为虚拟的,在子类中覆盖它并向下转换。
#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();
}
在我的结论中:
- 可以使用引用在抽象基类中调用赋值运算符。
- 规则 12-8-2 旨在防止切片问题。
- 如果没有切片问题,我不必总是应用规则 12-8-2。
BЈовић:但是,如果你的类真的很抽象(意味着它有纯虚方法),那么这个规则就没有意义了。
确实如此,因为赋值运算符可能会意外地被调用到基类的指针/引用,这总是会导致切片。
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
该规则确实有意义,原因如下:
- 防止部分分配
- 防止在不兼容的类之间进行复制
假设我们有一个抽象的基类Animal
和几个派生类,Lizard
和Chicken
:
class Animal {
public:
...
};
class Lizard: public Animal {
...
};
class Chicken: public Animal {
...
};
默认情况下,赋值运算符是公共的。现在考虑以下代码:
Lizard liz1;
Lizard liz2;
Animal *pAnimal1 = &liz1;
Animal *pAnimal2 = &liz2;
...
*pAnimal1 = *pAnimal2;
在最后一行调用的赋值运算符是 Animal
类的赋值运算符,即使所涉及的对象是 Lizard 类型。因此,只会修改 liz1 的Animal
部分。这是部分作业。分配后,liz1 的Animal
成员具有他们从 liz2 获得的值,但 liz1 的Lizard
成员保持不变。
也可以通过Animal
指针将Lizard
分配给Chicken
。这没有多大意义,因为它们彼此不兼容,我们可能希望防止这样的事情发生。防止此类分配的最简单方法是在Animal
中保护operator=
。这样,可以将蜥蜴分配给蜥蜴,也可以将鸡分配给鸡,但禁止部分和混合类型的分配:
class Animal {
protected:
Animal& operator=(const Animal& rhs);
...
};
或者,如果此类层次结构中禁止复制,则可以=delete
定义赋值运算符。实际上,私有或受保护的赋值运算符根本不需要返回*this
。他们可以返回类型 void。
- 基类和派生类的多态赋值运算符
- 为什么基类中的复制和交换会导致派生类中的复制赋值运算符被隐式删除?
- 无法在赋值运算符中访问基类的受保护方法
- 模板类赋值运算符
- 在基中使用派生类的赋值运算符
- 如何使用继承(抽象基类)实现移动构造函数和移动赋值运算符
- 默认赋值运算符有权访问基类的私有成员
- 如何重载赋值运算符以允许我的类等于基元类型,例如'int'
- 为什么派生类不使用基类运算符=(赋值运算符)?
- 在将抽象基类作为参数的函数中将指针和赋值运算符与派生类一起使用
- 模板类赋值运算符中的模板参数
- 模板类赋值运算符重载
- 我可以从子类以外的另一个类调用抽象基类的公共赋值运算符吗?
- c++中的字符串类赋值运算符重载
- 使用"using"声明隐藏基类方法不适用于赋值运算符
- 为C++中具有多个继承派生类的vtables的基之一调用赋值运算符
- 派生类继承基类赋值运算符
- 派生类是否间接继承基的赋值运算符?
- C++:在派生类构造函数中调用基类赋值运算符的形式不正确
- 在子类类中重载基类赋值操作符会导致二义性赋值错误