现在,如果有这样的要求需要实现modifyShape函数...那么这个函数应该在基类中如何实现呢?
如何实现这个函数是一个主观问题,但我们可以通过以下方式来解决:
1.认识到这个函数可能会有哪些实现方式;
2.根据一些“最佳实践”建议来制定替代方案。
C++核心准则通常被称为“最佳实践”指南,它建议优先选择具体的常规类型。我们可以使用这个指南来回答这个问题,并提供一个这个函数和设计可能的实现方式。
首先,要理解多态类型和多态行为之间的区别。
多态类型是具有或继承至少一个虚函数的类型。这个形状
类及其虚拟displayArea
成员函数就是这样一种多态类型。在C++术语中,这些都是满足std::is_polymorphic_v<T>
返回true
的类型T
。
与非多态类型相比,多态类型在以下几个方面存在差异:
- 它们需要通过引用或指针来处理,以避免切片问题。
- 它们不是自然正则的。也就是说,它们不能像基本的C++类型
int
一样对待。
所以根据您提供的设计,以下代码将无法工作,但指导意见是它确实能工作:
auto myShape = shape{triangle{1.0, 2.0, 2.0}};
myShape.displayArea();
myShape = circle(4);
myShape.displayArea();
同时,更重要的是
shape
的多态行为,这样一个形状可以是一个圆形或三角形。使用多态类型是一种提供多态行为的方式,就像你展示的那样,但这并非唯一的方式,它也存在问题,比如你问到怎么解决的问题。
另一种提供多态行为的方式是使用标准库类型,比如
std::variant
,然后像下面这样定义
shape
:
class circle {
int radius;
public:
circle(int radius2) :radius(radius2){ }
void displayArea() {
double area = 3.14*radius*radius;
std::cout << " \n Area circle" << area<<std::endl;
}
};
class triangle {
double a,b,c;
public:
triangle(double a1, double b1, double c1): a(a1), b(b1),c(c1) {
if (a + b > c && a + c > b && b + c > a)
std::cout << "The sides form a triangle" << std::endl;
else
std::cout << "The sides do not form a triangle. Correct me !" << std::endl;
}
void displayArea() {
double s = (a + b + c) / 2;
double area = sqrt(s*(s - a)*(s - b)*(s - c));
std::cout << " \n Area triangle"<< area<<std::endl;
}
};
using shape = std::variant<triangle,circle>;
auto myShape = shape{triangle{1.0, 2.0, 2.0}};
myShape = triangle{3.0, 3.0, 3.0};
而且我们可以编写一个“形状”访问函数来调用适当的“显示面积”。
虽然这样的解决方案更加“规范”,但使用“std::variant”在分配给其他类型的形状时是不开放的(除了它所定义的类型),而像“myShape = rectangle{1.5, 2.0};”这样的代码将无法工作。
与其使用“std::variant”,我们可以使用“std::any”。这将避免只支持为其定义的形状(就像使用“std::variant”一样)的缺点。使用此“形状”的代码可能如下所示:
auto myShape = shape{triangle{1.0, 2.0, 2.0}};
myShape = triangle{3.0, 3.0, 3.0};
std::any_cast<triangle&>(mShape).displayArea();
myShape = rectangle{1.5, 2.0};
std::any_cast< rectangle&>(mShape).displayArea();
然而,使用
std::any
的缺点是它不会根据值类型提供的任何概念功能来限制它可以接受的值。
我将描述Sean Parent在他的演讲
Inheritance Is The Base Class of Evil和其他地方中描述的解决方案。人们似乎正在称这些类型为:多态值类型。我喜欢将这个解决方案描述为扩展更熟悉的
pointer to implementation (PIMPL)惯用语法之一。
以下是一个多态值类型的示例(为了更容易说明,省略了一些内容),用于
shape
类型:
class shape;
void displayArea(const shape& value);
class shape {
public:
shape() noexcept = default;
template <typename T>
shape(T arg): m_self{std::make_shared<Model<T>>(std::move(arg))} {}
template <typename T, typename Tp = std::decay_t<T>,
typename = std::enable_if_t<
!std::is_same<Tp, shape>::value && std::is_copy_constructible<Tp>::value
>
>
shape& operator= (T&& other) {
shape(std::forward<T>(other)).swap(*this);
return *this;
}
void swap(shape& other) noexcept {
std::swap(m_self, other.m_self);
}
friend void displayArea(const shape& value) {
if (value.m_self) value.m_self->displayArea_();
}
private:
struct Concept {
virtual ~Concept() = default;
virtual void displayArea_() const = 0;
};
template <typename T>
struct Model final: Concept {
Model(T arg): data{std::move(arg)} {}
void displayArea_() const override {
displayArea(data);
}
T data;
};
std::shared_ptr<const Concept> m_self;
};
struct circle {
int radius = 0;
};
void displayArea(const circle& value) {
}
struct triangle {
double a,b,c;
};
void displayArea(const triangle& value) {
}
auto myShape = shape{triangle{1.0, 2.0, 2.0}};
displayArea(myShape);
myShape = circle{4};
displayArea(myShape);
mShape = circle{5};
这里有一个链接,基本上展示了这段代码的编译情况,以及这个shape
是一个具有多态行为的非多态类型。
虽然这种技术在使事情正常工作方面带来了负担,但也有努力让它变得更容易(例如P0201R2)。此外,对于已经熟悉PIMPL习惯用法的程序员来说,我不认为接受这一点会很困难,因为需要从引用语义和继承思维转向值语义和组合思维。
if (typestring=="Triangle") ((Triangle*) shapeptr)->modifyTriangle(arg1, arg2, arg3);
。 - Topological Sort