【问题标题】:Can I call abstract base class's public assignment operator from another class except subclass?我可以从除子类之外的另一个类调用抽象基类的公共赋值运算符吗?
【发布时间】:2013-09-20 06:30:01
【问题描述】:

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

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

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

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

我是否忽略了什么?

【问题讨论】:

    标签: c++


    【解决方案1】:

    如果他们认为具有虚函数(非纯)的类是抽象的,那么很可能会阻止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
    }
    

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

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

    【讨论】:

    • 来自未来的你好,这不是 100% 准确的,因为如果你有一个名为 ref 的 A 引用,那么 ref = b;将调用 A::operator= 和 slice。
    【解决方案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 检查兼容性。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-08-09
            • 1970-01-01
            • 2012-12-04
            • 2019-04-16
            • 1970-01-01
            • 1970-01-01
            • 2016-07-10
            相关资源
            最近更新 更多