【问题标题】:Overloading assignment operator and upcasting重载赋值运算符和向上转型
【发布时间】:2021-07-05 12:10:27
【问题描述】:

我想知道在使用继承和向上转换时我应该如何(或者我可以吗?这有意义吗?)重载赋值运算符? 假设我们有 Base 类和 Derived 类(继承自 Base)。如果我有类似的东西:

/// supose we have one overloaded assignment operator in Base like Base& operator=(const Base&) and 
///one in Derived like Derived& operator=(const Derived&)...
Base* a, *b;
Derived c,d;

a = &c;
b = &d;

*a = *b  /// this will call the function in Base

如果调用 Base 函数,我为什么要在 Derived 中重载“=”? Derived 中的重载赋值运算符是否仅用于直接处理对象而不是向上转换(指针)?

【问题讨论】:

  • 你的意思是Base* a, *b;
  • @fabian 这不会是一个常规的= 运算符,不是吗,因为它会强制要求类似Derived::operator=(const B&) 之类的东西,不是吗?
  • @Zoso 是的,现在已编辑。
  • 我添加了编辑,以便我的答案包含一个示例,其中 Derived 管理和不管理自己的动态资源。

标签: c++ oop inheritance upcasting


【解决方案1】:

这里有一些代码希望能帮到你。

Derived 不拥有动态资源
Base 类拥有一个动态资源,因此我们需要遵循 3 规则(应该是 5,但为简洁起见保持为 3)。我通过使用复制/交换习语来做到这一点。

然后我从Base 派生Derived。它不保存动态资源,所以我遵循 0 规则,不提供自定义复制构造函数、析构函数、赋值运算符、移动构造函数或移动赋值。

您可以从输出中看到Derived 对象的Base 部分能够很好地深度复制它们,而Derived-only 部分可以很好地使用浅层复制。最终输出会泄漏内存,但我选择这样做是为了演示使用指向 Base 的指针进行实​​际覆盖。

#include <iostream>

class Base {
 private:
  int* m = nullptr;

 public:
  Base() = default;
  Base(int v) : m(new int(v)) {}
  Base(const Base& other) : m(new int(*(other.m))) {}
  virtual ~Base() {
    delete m;
    m = nullptr;
  }

  Base& operator=(Base other) {
    swap(*this, other);

    return *this;
  }

  friend void swap(Base& lhs, Base& rhs) {
    using std::swap;

    swap(lhs.m, rhs.m);
  }

  virtual void print() const {
    std::cout << "Address: " << m << "\nValue: " << *m << '\n';
  }
};

class Derived : public Base {
 private:
  double x = 0.0;

 public:
  Derived() = default;
  Derived(double v) : Base(), x(v) {}
  Derived(int i, double v) : Base(i), x(v) {}

  void print() const override {
    std::cout << "Address: " << &x << "\nValue: " << x << '\n';
    Base::print();
  }
};

int main() {
  std::cout << "A\n";
  Base* a = new Derived(5, 3.14);
  a->print();

  std::cout << "\nB\n";
  Derived b = *(dynamic_cast<Derived*>(a));  // Copy ctor
  b.print();

  std::cout << "\nC\n";
  Derived c;
  c = b;
  c.print();

  std::cout << "\nReplace A (This leaks)\n";
  a = new Derived(7, 9.81);
  a->print();
}

输出:

A
Address: 0x21712d0
Value: 3.14
Address: 0x21712e0
Value: 5

B
Address: 0x7ffdd62964c8
Value: 3.14
Address: 0x2171300
Value: 5

C
Address: 0x7ffdd62964b0
Value: 3.14
Address: 0x2171320
Value: 5

Replace A (This leaks)
Address: 0x2171350
Value: 9.81
Address: 0x2171360
Value: 7

Derived 拥有动态资源
现在,Derived 拥有自己的动态来管理。所以我遵循规则 3 并提供复制构造函数、析构函数和赋值运算符重载。您会注意到赋值运算符看起来与Base 版本相同;这是故意的。

这是因为我使用的是复制/交换习语。所以在 Derived 的 swap() 函数中,我添加了一个步骤,它交换 Base 部分,然后交换 Derived 部分。我通过动态转换调用 Base swap() 函数来做到这一点。

我们可以再次观察到,所有对象对于每个动态分配的块都有自己的内存。

#include <iostream>

class Base {
 private:
  int* m = nullptr;

 public:
  Base() = default;
  Base(int v) : m(new int(v)) {}
  Base(const Base& other) : m(new int(*(other.m))) {}
  virtual ~Base() {
    delete m;
    m = nullptr;
  }

  Base& operator=(Base other) {
    swap(*this, other);

    return *this;
  }

  friend void swap(Base& lhs, Base& rhs) {
    using std::swap;

    swap(lhs.m, rhs.m);
  }

  virtual void print() const {
    std::cout << "Address: " << m << "\nValue: " << *m << '\n';
  }
};

class Derived : public Base {
 private:
  double* x = nullptr;

 public:
  Derived() = default;
  Derived(double v) : Base(), x(new double(v)) {}
  Derived(int i, double v) : Base(i), x(new double(v)) {}
  Derived(const Derived& other) : Base(other), x(new double(*(other.x))) {}
  ~Derived() {
    delete x;
    x = nullptr;
  }

  Derived& operator=(Derived other) {
    swap(*this, other);

    return *this;
  }

  friend void swap(Derived& lhs, Derived& rhs) {
    using std::swap;

    swap(dynamic_cast<Base&>(lhs), dynamic_cast<Base&>(rhs));
    swap(lhs.x, rhs.x);
  }

  void print() const override {
    std::cout << "Address: " << &x << "\nValue: " << *x << '\n';
    Base::print();
  }
};

int main() {
  std::cout << "A\n";
  Base* a = new Derived(5, 3.14);
  a->print();

  std::cout << "\nB\n";
  Derived b = *(dynamic_cast<Derived*>(a));  // Copy ctor
  b.print();

  std::cout << "\nC\n";
  Derived c;
  c = b;
  c.print();

  std::cout << "\nReplace A (This leaks)\n";
  a = new Derived(7, 9.81);
  a->print();
}

输出:

A
Address: 0x14812d0
Value: 3.14
Address: 0x14812e0
Value: 5

B
Address: 0x7fffe89e8d68
Value: 3.14
Address: 0x1481320
Value: 5

C
Address: 0x7fffe89e8d50
Value: 3.14
Address: 0x1481360
Value: 5

Replace A (This leaks)
Address: 0x14813b0
Value: 9.81
Address: 0x14813c0
Value: 7

【讨论】:

    【解决方案2】:

    此示例的输出是否有助于澄清您的问题?您可以随时覆盖派生类中的operator=,如下所示:

    #include <cstdio>
    
    struct Base{
    virtual ~Base() = default;
    
    virtual void operator=(const Base&) {
        std::printf("Base::=\n");
    }
    
    };
    
    struct Derived: public Base {
        void operator=(const Derived&) {
            std::printf("Derived::=\n");
        }
        void operator=(const Base&) override{
            std::printf("Derived::= Base\n");
        }
    };
    
    
    int main() {
        Base* a, *b;
        Derived c,d;
    
        a = &c;
        b = &d;
    
        *a = *b; //Dispatches the call to the derived class =
    
        Base base;
        Derived derived;
        derived = base; //Usual case now after operator=(const Base&) in Derived
    
        c = d; //Usual case
    
        Base base1, base2;
        base1 = base2; //Usual case
        a = &base1;
        b = &base2;
    
        *a = *b; //Usual case
    }
    

    Output:

    Derived::= Base
    Derived::= Base
    Derived::=
    Base::=
    Base::=
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-30
      • 2013-02-14
      • 2016-08-30
      • 1970-01-01
      • 1970-01-01
      • 2013-03-15
      • 2016-09-05
      相关资源
      最近更新 更多