重载赋值运算符和向上转型

Overloading assignment operator and upcasting

我想知道在处理继承和向上转型时我应该如何(或者我可以?这有意义吗?)重载赋值运算符? 假设我们有 Base class 和 Derived class(继承自 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 中的重载赋值运算符是否仅在直接使用对象而不是向上转换(指针)时才需要?

此示例的输出是否有助于澄清您的问题?您始终可以覆盖派生 class 中的 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::=

这里有一些代码,希望对您有所帮助。

Derived 不拥有动态资源
Base class 拥有一个动态资源,所以我们需要遵循 3 规则(应该是 5,但为简洁起见保持在 3)。我通过使用 copy/swap 成语来做到这一点。

然后我从 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 版本相同;这是故意的。

因为我用的是copy/swap成语。因此,在 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