我可以从除子类之外的另一个类调用抽象基类的公共赋值运算符吗?

时间:2013-09-20 06:30:01

标签: c++

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

  

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

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

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

我忽略了什么吗?

5 个答案:

答案 0 :(得分:3)

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

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

  

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

是否定义赋值运算符并不重要。如果你不这样做,编译器会为你生成一个。

答案 1 :(得分:2)

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

此问题的早期答案还有一个主要问题,即:&#34;切片问题&#34;这在多态类中变得更成问题,因为赋值运算符默认情况下不是虚拟的。

考虑一下

class A
{
};

class B : public A
{
}

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

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

答案 2 :(得分:0)

根据定义,抽象类无法实例化(例如,请参阅here)。无法复制无法实例化的类。因此,这条规则是荒谬的。

答案 3 :(得分: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。

答案 4 :(得分: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