抽象基类如何避免部分赋值

How does the abstract base class avoid the partial assignment?

本文关键字:赋值 何避免 基类 抽象      更新时间:2023-10-16

正如"更有效的C++"中第33项所述,分配问题是

//Animal is a concrete class
Lizard:public Animal{};
Chicken:public Animal{};
Animal* pa=new Lizard('a');
Animal* pb=new Lizard('b');
*pa=*pb;//partial assignment

然而,如果我将Animal定义为一个抽象基类,我们也可以编译并运行句子:*pa=*pb。部分分配问题仍然存在
参见我的示例:

#include <iostream> 
class Ab{ private: int a;
        double b;
    public:
        virtual ~Ab()=0;
};
Ab::~Ab(){}
class C:public Ab{
    private:
        int a;
        double b;
};
class D:public Ab{
    private:
        int a;
        double b;
};
int main()
{
    Ab *pc=new C();
    Ab *pd=new D();
    *pc=*pd;
    return 0;
}

我错过什么了吗?那么抽象基类的真正含义是什么呢?

我自己得到了答案。我漏掉了书中的一段代码
在基类中使用受保护的operator=以避免使用*pa=*pb。使用抽象基类来避免animal1=animal2。那么唯一允许的表达式是lizard1=lizard2;chicken1=chicken2;
请参阅以下代码:

#include <iostream> 
class Ab{ 
    private: 
        int a;
        double b;
    public:
        virtual ~Ab()=0;
    protected:   //!!!!This is the point
        Ab& operator=(const Ab&){...}
};
Ab::~Ab(){}
class C:public Ab{
    public: 
        C& operator=(const C&){...}
    private:
        int a;
        double b;
};
class D:public Ab{
    public:
        D& operator=(const D&){...}
    private:
        int a;
        double b;
};
int main()
{
    Ab *pc=new C();
    Ab *pd=new D();
    *pc=*pd;
    return 0;
}

在赋值的情况下,抽象基类没有帮助,因为基子对象没有实例化(抽象类会阻止什么),而是从派生对象中切片(即,在现有的基子对象之间进行赋值)。

为了避免这个问题,我唯一能想到的解决方案就是

  1. 使分配虚拟化
  2. 请在分配中检查源实例的类型是否正确

代码内

#include <iostream>
struct Base {
    int bx;
    Base(int bx) : bx(bx) {}
    virtual Base& operator=(const Base& other) {
        bx = other.bx;
        return *this;
    }
};
struct A : Base {
    int x;
    A(int bx, int x) : Base(bx), x(x) {}
    A& operator=(const Base& other) {
        const A& other_a = dynamic_cast<const A&>(other);
        Base::operator=(other);
        x = other_a.x;
        return *this;
    }
};
struct B : Base {
    int x;
    B(int bx, int x) : Base(bx), x(x) {}
    B& operator=(const Base& other) {
        const B& other_b = dynamic_cast<const B&>(other);
        Base::operator=(other);
        x = other_b.x;
        return *this;
    }
};

如果传递给赋值运算符的对象不是正确的派生类型(它可以是子派生对象,但对于赋值来说,这在逻辑上应该是可以的),则dynamic_cast<const A&>(other)操作将失败。

例如:

int main(int argc, const char *argv[]) {
    Base *pa1 = new A(1, 2);
    Base *pa2 = new A(3, 4);
    Base *pb1 = new B(5, 6);
    Base *pb2 = new B(7, 8);
    *pa1 = *pa2; std::cout << pa1->bx << "/" << dynamic_cast<A*>(pa1)->x << "n";
    *pb1 = *pb2; std::cout << pb1->bx << "/" << dynamic_cast<B*>(pb1)->x << "n";
    std::cout << "Ok so farn";
    *pa1 = *pb1; // Runtime error here (bad cast)
    return 0;
}

基类具有纯虚拟函数并不重要,因为您还没有为任何类定义operator=。因此,当编译器看到以下语句时:

*pc=*pd;

当pc和pd都是Ab类型时,它将调用Ab的默认赋值运算符,这将导致部分赋值。在下面的例子中,我得到的输出是"Abstract Base",它来自抽象基类:

class A {
public:
    virtual void foo() =0;
    virtual A& operator=(const A& rhs) {
        std::cout << "Abstract Base";
            return *this;
    }
};

class B : public A {
public:
    virtual void foo() {
        std::cout << "b:foo";
    }
};
class C : public A {
public:
    virtual void foo() {
        std::cout << "c:foo";
    }
};
int main()
{
    A* b = new B();
    A* c = new C();
    *b = *c;
    return 0;
}

由于您没有处理类中的赋值运算符,您遇到了Scot在文章中明确描述的部分赋值情况。

你需要处理课堂上的作业。在当前的设计中,默认调用Ab的隐式赋值运算符,从而丢失子类的所有属性。

为了避免这种情况,您应该有这样的实现:

   class Ab{ private: int a;
        double b;
    public:
        virtual ~Ab()=0;
        virtual Ab& operator=(const Ab& rhs){cout<<"in Ab="<<endl;} 
};
Ab::~Ab(){}
class C:public Ab{
    C& operator=(const Ab& rhs){cout<<"in C="<<endl;
      return operator=(dynamic_cast<const C&>(rhs)); }
    C& operator=(const C& rhs){
       cout<<"do somethin in C="<<endl;
   }
    private:
        int a;
        double b;
};
class D:public Ab{
D& operator=(const Ab& rhs){cout<<"in D="<<endl;
      return operator=(dynamic_cast<const D&>(rhs)); }
    D& operator=(const D& rhs){
       cout<<"do somethin in D="<<endl;
   }
    private:
        int a;
        double b;
};