C ++ - 拥有派生类的安全/标准方法"继承"来自基类的重载赋值运算符

时间:2014-09-02 17:58:49

标签: c++ class inheritance operator-overloading overloading

据我所知,在C ++中,派生类不会从基类继承重载的赋值运算符。我在下面写了一个例子,我明确地重载了​​基类和派生类的赋值运算符。在底部的示例输出中,有一个部分:

Index:1 - Base Value:1
Index:1 - Derived Value:2
Index:2 - Base Value:2
Index:2 - Derived Value:2

预期的输出应该是:

Index:1 - Base Value:2
Index:1 - Derived Value:2
Index:2 - Base Value:2
Index:2 - Derived Value:2 

此输出并非意外。我意识到赋值运算符不是继承的。有一个事实上的安全/标准方法让派生类调用基类的赋值运算符吗?到目前为止,我最好的猜测是让派生类的赋值运算符函数将派生类对象向上转换为基类对象,并将RHS分配给LHS。这是一种普遍接受的安全方法吗?

如果我使用"建议的解决方案" (页面底部),它可以工作,但只是因为没有一个运算符被声明为virtual。如果我向上转换并使用非虚函数,则使用的函数版本取决于强制转换,而如果我使用虚函数,则使用的成员函数取决于对象的实际类型而不是它的实际类型。施展为。

谢谢。

代码清单


/*******************************************************************************
 * Preprocessor Directives
 ******************************************************************************/
#include <iostream>
using namespace std;


/*******************************************************************************
 * Class Declarations and Function Prototypes
 ******************************************************************************/
class Base {
   private:
   protected:
   public:
      int iBInt;
      Base();        /* Constructor */
      Base(int a);   /* Constructor - Set const member */
      Base & operator=(const Base& rhs);
      virtual ~Base();
};

class Derived : public Base {
   private:
   protected:
   public:
      int iDInt;
      Derived();        /* Constructor */
      Derived(int a);   /* Constructor - Set const member */
      Derived & operator=(const Derived& rhs);
      ~Derived();
};

/*******************************************************************************
 * Class and Function Definitions
 ******************************************************************************/
/******************************************************************************/
Base::Base(void) {
   cout << __FUNCTION__ << endl;
   iBInt = 0;
   cout << "iBInt: " << iBInt << endl;
}

/******************************************************************************/
Base::Base(int a) {
   cout << __FUNCTION__ << endl;
   iBInt = a;
   cout << "iBInt: " << iBInt << endl;
}

/******************************************************************************/
Base::~Base(void) {
   cout << __FUNCTION__ << endl;
}

/******************************************************************************/
Base& Base::operator=(const Base& rhs) {
   cout << "Base::" << __FUNCTION__ << endl;
   if (this == &rhs) {
      return *this;
   }
   iBInt = rhs.iBInt;
   cout << "iBInt: " << iBInt << endl;
   return *this;
}

/******************************************************************************/
Derived::Derived(void) {
   cout << __FUNCTION__ << endl;
   iDInt = 0;
   cout << "iDInt: " << iDInt << endl;
}

/******************************************************************************/
Derived::Derived(int a) : Base(a) {
   cout << __FUNCTION__ << endl;
   iDInt = a;
   cout << "iDInt: " << iDInt << endl;
}

/******************************************************************************/
Derived::~Derived(void) {
   cout << __FUNCTION__ << endl;
}

/******************************************************************************/
Derived& Derived::operator=(const Derived& rhs) {
   cout << "Derived::" << __FUNCTION__ << endl;
   if (this == &rhs) {
      return *this;
   }
   iDInt = rhs.iDInt;
   cout << "iDInt: " << iDInt << endl;
   return *this;
}


/*******************************************************************************
 * Main Entry Point
 ******************************************************************************/
int main(void) {
   int count = 3;
   /* Generate objects */
   Derived **bArr = new Derived*[count];
   for (int i=0; i<count; i++) {
      bArr[i] = new Derived(i);
   }

   /* Set some values via overloaded assignment operator, and print out
    * updated values.
    */
   for (int i=0; i<count; i++) {
      cout << "Index:" << i << " - Base Value:" << bArr[i]->iBInt << endl;
      cout << "Index:" << i << " - Derived Value:" << bArr[i]->iDInt << endl;
   }
   *bArr[1] = *bArr[2];
   for (int i=0; i<count; i++) {
      cout << "Index:" << i << " - Base Value:" << bArr[i]->iBInt << endl;
      cout << "Index:" << i << " - Derived Value:" << bArr[i]->iDInt << endl;
   }
   /* Cleanup */
   for (int i=0; i<count; i++) {
      delete bArr[i];
   }
   delete [] bArr;

   return 0;
}

示例输出


Base
iBInt: 0
Derived
iDInt: 0
Base
iBInt: 1
Derived
iDInt: 1
Base
iBInt: 2
Derived
iDInt: 2
Index:0 - Base Value:0
Index:0 - Derived Value:0
Index:1 - Base Value:1
Index:1 - Derived Value:1
Index:2 - Base Value:2
Index:2 - Derived Value:2
Derived::operator=
iDInt: 2
Index:0 - Base Value:0
Index:0 - Derived Value:0
Index:1 - Base Value:1
Index:1 - Derived Value:2
Index:2 - Base Value:2
Index:2 - Derived Value:2
~Derived
~Base
~Derived
~Base
~Derived
~Base

建议的解决方案/修改


/******************************************************************************/
Derived& Derived::operator=(const Derived& rhs) {
   cout << "Derived::" << __FUNCTION__ << endl;
   if (this == &rhs) {
      return *this;
   }
   Base* b1 = this;
   Base* b2 = (Base*)&rhs;
   *b1 = *b2;
   iDInt = rhs.iDInt;
   cout << "iDInt: " << iDInt << endl;
   return *this;
}

2 个答案:

答案 0 :(得分:7)

我说直接调用Base赋值运算符:

Base::operator =(rhs);

调用继承的类&#39;这种方式的赋值运算符比用指针体操调用它更干净和直接。


另一种方法是:

static_cast<Base &>(*this) = rhs;

这比使用指针调用运算符更干净,但仍然(IMO)比显式调用基本运算符重载更不易读。

答案 1 :(得分:2)

  

是否有一个事实上的安全/标准方法来获得派生类   调用基类的赋值运算符?

赋值运算符(以及一般的运算符)几乎总是暗示值语义。分配给字符串,向量,地图,日期或int是有意义的。

虚拟功能几乎总是暗示完全相反。分配给流,窗口或数据库实现没有意义。几乎所有使用虚函数的类都应禁用复制和复制赋值。

您遇到的所有问题都是由混合难以混合的东西的明显愿望引起的,而且通常不应该混合

我建议你禁用基类中的复制,并将赋值运算符重命名为常规成员函数:

class Base : boost::noncopyable { // in C++11, use `= delete`
   public:
      int iBInt;
      Base();        /* Constructor */
      Base(int a);   /* Constructor - Set const member */
      virtual ~Base();
      virtual void Assign(Base const &other);
};

你几乎肯定会找到一个比Assign更具描述性的名字,很有可能你很可能会发现调用这个函数是完全错误的&#34;分配&#34;首先。

事实上,现在该功能具有正常的名称,您可以重新评估整个班级设计,而不会分散注意力&#34;通过运算符语法。

可能的担忧:

  • 在面向对象的编程中,为了调用父实现而重写函数通常被认为是错误的样式,因为这意味着基类不能充分建立不变量。

  • 在基类虚函数中使用基类参数可能会导致非常复杂的设计,因为您正在尝试使函数虚拟化为两个参数。无形&#34;隐形&#34; thisother可以是层次结构中的任何类。可能是BaseDerived的四种组合中的任何一种都需要做四件不同的事情吗?如果(应该预期)更多的派生类进入游戏怎么办?这里的面向对象编程是否仅用于在源代码中使用类似的实现,而不是将它们集中在一个地方?