在以抽象基类为参数的函数中将指针和赋值运算符与派生类一起使用

Posted

技术标签:

【中文标题】在以抽象基类为参数的函数中将指针和赋值运算符与派生类一起使用【英文标题】:Using pointers and assignment operator with derived class in a function that takes abstract base class as argument 【发布时间】:2016-06-04 14:53:58 【问题描述】:

我有一个函数 (modShape),它以抽象基类 (Shape) 作为参数;在函数中,我想复制输入对象,修改副本,然后将副本重新分配给输入对象,以便将修改保留在 modShape 的范围之上。

我已经设置了一个 clone() 成员函数来制作初始副本,这似乎运作良好。接下来,我使用 doubleArea() 成员函数修改副本,并尝试将其复制回输入对象。

基类和派生类在 header.h 中定义:

#ifndef HEADER_H_
#define HEADER_H_

#include <iostream>
#include <cmath>

using namespace std;

// Abstract base class.
class Shape 
 public:
  // Virtual functions
  virtual double area()  return 0; 
  virtual double perimeter()  return 0; 
  virtual void doubleArea()  /* do nothing */ 
  virtual Shape* clone() const = 0;

;

// Derived class.
class Circle: public Shape 
 private:
  double radius;

 public:
  Circle (double r) : radius(r) 
  double area()  return (M_PI*pow(radius,2)); 
  double perimeter()  return (M_PI*2*radius); 
  void doubleArea()  radius *= pow(2,0.5); 
  Circle* clone() const  return new Circle(*this); 

;

#endif

函数modShape和测试代码在main.cpp中:

#include <iostream>
#include "header.h"

using namespace std;

void modShape(Shape &inShape) 

  // Make new Shape* from clone of inShape
  // and double its area.
  Shape* newShape = inShape.clone();
  newShape->doubleArea();
  cout << "newShape's area (after doubling): " << newShape->area() << endl;

  // Copy newShape to inShape.
  inShape = *newShape;
  cout << "newShape copied to inShape (circ)." << endl;
  cout << "inShape's area in modShape: " << inShape.area() << endl;

;


int main() 

  Circle circ(2);
  cout << "circ's initial area (in main): " << circ.area() << endl;
  modShape(circ);
  cout << "circ's final area (in main): " << circ.area() << endl;

  return 0;

我从这个函数得到的输出是:

circ的初始区域(主要):12.5664

newShape 的面积(加倍后):25.1327

newShape 复制到 inShape。

modShape()中inShape的面积:12.5664

circ的最终区域(主要):12.5664

很明显,inShape = *newShape 的分配没有像我预期的那样工作。我的猜测是,正在使用的赋值运算符是用于 Shape 类的,因此不会从派生类中复制成员变量(如半径)?如果是这种情况,我想我想定义一个赋值运算符,它将“知道”对象是派生类,即使它们被定义为基类,但我不知道该怎么做。或者如果有更好的解决方案,请告诉我!非常感谢任何建议。

更新: 看起来切片是问题,现在我需要弄清楚如何避免它。我想如果我定义我的函数来接收一个指针,事情会更好:

void modShape2(Shape* inShape) 
  Shape* newShape = inShape->clone();
  cout << inShape->area() << endl;
  inShape = newShape;
  cout << inShape->area() << endl;

我设置为:

Circle *circ2 = new Circle(1);
cout << "circ2's initial area (in main): " << circ2->area() << endl;
modShape2(circ2);
cout << "circ2's final area (in main): " << circ2->area() << endl;

这里产生的输出是

circ2的最终区域(主要):3.14159

3.14159

6.28319

circ2的最终区域(主要):3.14159

在这种情况下,似乎复制是在没有切片的情况下发生的,因为该区域在 modShape2 函数内被加倍,但是当我们出于某种原因超出 modShape2 的范围时,这些更改不会被执行。我真的对此感到困惑!

【问题讨论】:

在你的“更新”中,记住C++使用传值,所以改变局部变量inShape在函数外没有影响 多态层次的赋值运算符的主题是一个困难的话题,see this thread 用于一些讨论和方法。一种选择当然是禁用赋值运算符并要求用户(删除和)克隆。 你是对的!我也尝试修改指向的对象,但我相信这会导致相同的切片问题。 【参考方案1】:

问题

您已经很好地识别了它。错误是由以下语句引起的,导致object slicing:

inShape = *newShape;

所以只有 Shape 基础对象中的成员被复制。不属于基类的区域不会被复制。

如何鞋底?

不建议定义虚拟赋值运算符,因为该运算符对于类 T 的通常签名是:

 T& operator= (const T& r);   

这样你就会在返回类型上遇到麻烦。

一个更简单的解决方案是拥有一个虚拟复制功能(与您的克隆功能相似的原理):

class Shape 
    ...
    virtual void copy(const Shape&r) = 0; 
;

它将为派生对象实现,检查类型是否匹配并使用类型的赋值运算符。例如:

void copy(const Shape&r) override 
    if (dynamic_cast<const Circle*>(&r)) 
        *this = *dynamic_cast<const Circle*>(&r);
    else throw (invalid_argument("ouch! circle copy mismatch"));

这里是an online demo。

【讨论】:

感谢您的回复,我认为切片是问题,但不知道正确的名称!我尝试重新定义我的函数以获取指向对象的指针,但是,对对象的更改并未超出函数的范围(请参阅我的帖子中的更新)。 我已经用一种方法完成了答案,让您几乎像以前一样保持逻辑。 T&amp; operator= (const T&amp; r);copy-assignment 运算符,但是您可以使用 Der&amp; operator=(const Base&amp; r);,它使用协变返回类型覆盖基类的复制分配。您建议的 copy 函数也可以命名为 operator= 你的第二个指针传递版本不起作用的原因是你只修改了modShape2()本地的poitner参数。您可以通过引用传递指针来使该版本工作。然而,这段代码是不安全的:覆盖指针会丢失指向原始对象的指针,这样你就会有内存泄漏。 @Christophe:非常感谢您提供明确的解决方案并回答我的其他问题!这对我来说效果很好。

以上是关于在以抽象基类为参数的函数中将指针和赋值运算符与派生类一起使用的主要内容,如果未能解决你的问题,请参考以下文章

包含指向派生模板类的基类指针的类的赋值运算符和复制构造函数

cpp(第十三章)

基类与派生类转换-指针赋值

C++的探索路12继承与派生之高级篇--派生类与赋值运算符及多重继承

C++的=重载问题,怎样为两个有相同成员的类赋值

在 C++ 中将派生类对象分配和访问到基类“指向指针”对象