4

我遇到了 MISRA C++ 2008 指南,本指南中的规则 12-8-2 说:

复制赋值运算符应在抽象类中声明为受保护或私有。

然后我想,当我公开一个抽象类的赋值运算符时,
是否可以从另一个类调用它,除了它的子类?
我认为这是不可能的。
如果这是真的,他们为什么要定义这个规则?

基本上,从类设计的角度来看,我不使用具有私有成员的抽象类,也没有在基类中定义赋值运算符。因此,通常不需要应用此规则。但是,如果有一个抽象基类的公共赋值运算符,我会将其设为受保护的(或如果可能的话是私有的),因为公开是没有意义的。您知道应用此规则的其他充分理由吗?

我有没有忽略什么?

4

5 回答 5

3

如果他们认为具有虚函数(不是纯函数)的类是抽象的,那么很可能会阻止切片。它的正常术语是基类。

#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
}

我没有在基类中定义赋值运算符。

是否定义赋值运算符都没有关系。如果你不这样做,编译器会为你生成一个。

于 2013-09-20T06:40:44.717 回答
2

抽象类是其实现未知但您知道它们将如何表现或与其他类交互的类。因此,您不太可能知道复制和赋值运算符中实际需要的抽象类的大小或其他详细信息。

主要问题也来自这篇文章的早期答案,讨论了“切片问题”,这在多态类中变得更加成问题,因为默认情况下赋值运算符不是虚拟的。

考虑这个

class A
{
};

class B : public A
{
}

int main()
{
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
}

现在在上述情况下,a_ref 被初始化为 b2 对象,但在下一行中,当它被分配给 b1 时,将调用 A 的 operator= 而不是 B 的 operator=,这可能会改变其他对象 b2。您可以想象 A 类和 B 类中不为空的情况。因此,规则是您要么将复制构造函数和赋值运算符设为私有而不是公共的,如果您在抽象类中将赋值运算符设为公共,则将其设为虚拟,并在每个派生实现中使用 dynamic_cast 检查兼容性。

于 2013-09-20T06:52:37.997 回答
0

根据定义,抽象类不能被实例化(例如,参见此处)。无法实例化的类无法复制。因此,这个规则是荒谬的。

于 2013-09-20T07:02:58.057 回答
0

我确认了@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。

于 2013-09-21T08:58:56.317 回答
0

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
于 2018-04-02T14:53:37.753 回答