在以抽象基数 class 作为参数的函数中使用指针和赋值运算符与派生 class
Using pointers and assignment operator with derived class in a function that takes abstract base class as argument
我有一个函数 (modShape),它以抽象基础 class (Shape) 作为参数;在函数中我想制作输入对象的副本,修改副本,然后将副本重新分配给输入对象,以便修改保留在 modShape 的范围之上。
我已经设置了一个 clone() 成员函数来制作初始副本,它似乎运行良好。接下来,我使用 doubleArea() 成员函数修改副本,并尝试将其复制回输入对象。
基础和派生 classes 在 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's initial area (in main): 12.5664
newShape's area (after doubling): 25.1327
newShape copied to inShape.
inShape's area in modShape(): 12.5664
circ's final area (in main): 12.5664
很明显,赋值 inShape = *newShape 没有像我预期的那样工作。我的猜测是正在使用的赋值运算符是针对形状 class,因此不会从派生的 class(如半径)复制成员变量?如果是这种情况,我想我想定义一个赋值运算符,它将 "know" 派生对象 classes,即使它们被定义为基础 classes,但我我不知道该怎么做。或者如果有更好的解决方案,请告诉我!非常感谢任何建议。
更新:
看起来切片是问题所在,现在我需要弄清楚如何避免它。我想如果我定义我的函数来接受一个指针,事情会更好:
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's final area (in main): 3.14159
3.14159
6.28319
circ2's final area (in main): 3.14159
在这种情况下,似乎复制是在没有切片的情况下发生的,因为该区域在 modShape2 函数内部被加倍了,但是当我们出于某种原因超出 modShape2 的范围时,更改没有进行。对此我真的很纳闷!
问题
你识别的很好。该错误是由以下语句引起的,它导致 object slicing:
inShape = *newShape;
因此仅复制 Shape 基础对象中的成员。不属于基地 class 的区域不会被复制。
如何鞋底?
不建议定义虚拟赋值运算符,因为此运算符对 class T 的通常签名是:
T& operator= (const T& r);
所以你会遇到 return 类型的问题。
一个更简单的解决方案,将有一个虚拟复制功能(与克隆功能相似的原理):
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。
我有一个函数 (modShape),它以抽象基础 class (Shape) 作为参数;在函数中我想制作输入对象的副本,修改副本,然后将副本重新分配给输入对象,以便修改保留在 modShape 的范围之上。
我已经设置了一个 clone() 成员函数来制作初始副本,它似乎运行良好。接下来,我使用 doubleArea() 成员函数修改副本,并尝试将其复制回输入对象。
基础和派生 classes 在 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's initial area (in main): 12.5664
newShape's area (after doubling): 25.1327
newShape copied to inShape.
inShape's area in modShape(): 12.5664
circ's final area (in main): 12.5664
很明显,赋值 inShape = *newShape 没有像我预期的那样工作。我的猜测是正在使用的赋值运算符是针对形状 class,因此不会从派生的 class(如半径)复制成员变量?如果是这种情况,我想我想定义一个赋值运算符,它将 "know" 派生对象 classes,即使它们被定义为基础 classes,但我我不知道该怎么做。或者如果有更好的解决方案,请告诉我!非常感谢任何建议。
更新: 看起来切片是问题所在,现在我需要弄清楚如何避免它。我想如果我定义我的函数来接受一个指针,事情会更好:
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's final area (in main): 3.14159
3.14159
6.28319
circ2's final area (in main): 3.14159
在这种情况下,似乎复制是在没有切片的情况下发生的,因为该区域在 modShape2 函数内部被加倍了,但是当我们出于某种原因超出 modShape2 的范围时,更改没有进行。对此我真的很纳闷!
问题
你识别的很好。该错误是由以下语句引起的,它导致 object slicing:
inShape = *newShape;
因此仅复制 Shape 基础对象中的成员。不属于基地 class 的区域不会被复制。
如何鞋底?
不建议定义虚拟赋值运算符,因为此运算符对 class T 的通常签名是:
T& operator= (const T& r);
所以你会遇到 return 类型的问题。
一个更简单的解决方案,将有一个虚拟复制功能(与克隆功能相似的原理):
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。