我试图理解is-a vs is-like-一种关系,我在某处读到,我们必须尝试遵循这样的设计,即我们总是拥有is-a关系,而不是is-like-a。考虑一下shape基类和派生的三角形和圆形类的经典示例。所以圆是一种形状,三角形也是一种形状。函数显示区域在基类中定义。现在,下面的程序运行良好。
#include "stdafx.h"
#include <cmath>
#include <iostream>
class shape
{
public:
virtual void displayArea()=0;
};
class circle :public shape
{
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 :public shape
{
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;
}
};
void main()
{
shape * p1[2];
p1[0]= new circle(20);
p1[1] = new triangle(5.6,8.1,10.3);
for (int i = 0; i < 2; ++i)
{
p1[i]->displayArea();
}
int y;
std::cin >> y;
}现在,如果需要实现modifyShape函数,其中形状的每个参数都根据用户的参数进行修改,那么我应该如何更改我的类,以使我的is-a关系不被更改。当我看着它的时候,我觉得我必须在圆形中定义一个参数modifyShape,在三角形中定义一个三参数modifyShape。但是这个函数在基类中应该是什么样子呢?
选项1:我在形状上定义了单参数和双参数modifyShape函数,但这意味着我将在圆形中有一个额外的两个参数函数,在三角形中有一个额外的一个参数函数。
选项2:我在shape中定义了一个可变参数函数modifyShape,但不知何故,这在我看来并不清晰。
发布于 2017-06-21 12:13:15
还有第三个选项可以使用,您可以创建一个新的类(或结构)层次结构,它将表示每个形状的参数。然后,您可以将指向基类的指针作为参数传递给虚函数。例如:
struct ShapeParams
{
...
}
struct TriangleParams : public ShapeParams
{
double a;
double b;
double c:
}
class shape
{
public:
virtual void displayArea()=0;
modifyShape (ShapeParams*) = 0;
};
class triangle :public shape
{
public:
void modifyShape (ShapeParams*) = override;
private:
TriangleParams m_params;
}发布于 2017-06-21 15:04:59
你可以稍微调整一下你的类,但这需要另一个独立的类。您可以创建一组2D和3D数学向量类,但您需要拥有vector可以执行的所有重载运算符和数学函数,例如加、减、乘向量或标量,如果是按向量,则需要担心点和叉积。你需要标准化方法,长度等等。一旦你有了这些可用的数学向量类。然后,您可以使用矢量重新设计您的形状类。或者,您可以不编写自己的向量类,而是使用一个数学库类,比如用于在OpenGL中工作的GLM的数学库。它是免费和开放源码的,也是一个只有头文件的库。一旦你将库安装到一个路径中,你需要做的就是包含它的头文件。你不必担心链接的问题。然后有了这些向量类,你的shape类中的数学运算就更容易做了,设计shape类也会更容易:下面是一个伪代码的例子:
#include <glm\glm.hpp>
// Needed If Doing Matrix Transformations: Rotation, Translation Scaling etc.
// #include <glm\gtc\matrix_transform.hpp>
class Shape {
public:
enum Type {
NONE = 0,
TRIANGLE,
SQUARE,
CIRCLE,
};
protected:
Type type_;
glm::vec4 color_ { 1.0f, 1.0f, 1.0f, 1.0f }; // Initialize List Set To White By Default
double perimeter_; // Also Circumference for Circle
double area_;
// double volume_; // If in 3D.
public:
// Default Constructor
Shape() : type_( NONE ), color_( glm::vec4( 1.0f, 1.0f, 1.0f, 1.0f ) ) {}
// User Defined Constructors
// Sets Shape Type Only Color Is Optional & By Default Is White
explicit Shape( Type type, glm::vec4 color = glm::vec4() ) : type_(type), color_( color ) {}
Type getType() const { return type_; }
void setType( Shape::Type type ) {
if ( type_ == NONE ) {
// Its okay to set a new shape type
type_ = type;
}
// We Already Have a Defined Shape
return;
}
// Getters That Are Commonly Found Across All Shapes
double getPerimeter() const { return perimeter_; }
double getArea() const { return area_; }
// Common Functions that can be done to any shape
void setSolidColor( glm::vec4 color ) { color_ = color };
glm::vec4 getColor() const { return color; }
// Common Interface That All Shapes Share But Must Override
virtual double calculateArea() = 0;
virtual double calculatePerimeter() = 0;
// Since we do not know what kind of shape to modify until we have one
// to work with, we do not know how many parameters this function will need.
// To get around this we can use a function template and then have overloads
// for each type we support
template<typename Type = Shape>
virtual void modify( Type* pShape /*,glm::vec3... params*/ );
// Overloaded Types: - Should Be Defined & Overridden By the Derived Class
virtual void modify<Triangle>( Triangle* pTriangle, glm::vec3, glm::vec3, glm::vec3, glm::vec4 = glm::vec4() ) { /* ... */ }
virtual void modify<Circle>( Cirlce* pCircle, float radius, glm::vec4 color = glm::vec4() ) { /* ... * / }
};那么继承的类看起来应该是这样的:
class Triangle : public Shape {
public:
// Could Be An Option To Where This is a base class as well to specific types of triangles:
enum TriangleType {
Acute = 0,
Right,
Equilateral,
Obtuse
} // then each of these would have properties specific to each type
private:
glm::vec3[3] vertices_;
public:
// Default Constructor
Triangle() : Shape(TRIANGLE) {} // Sets The Shape Type But Has No Vertices Or Area; just default construction
// Vertices But No Color
Triangle( glm::vec3 A, glm::vec3 B, glm::vec3 C ) : Shape(TRIANGLE) {
vertices_[0] = A;
vertices_[1] = B;
vettices_[2] = C;
// Call These To Have These Values
calculatePerimeter();
calculateArea();
}
// Vertices & Color
Triangle( glm::vec3 A, glm::vec3 B, glm::vec3 C, glm::vec4 color ) : Shape(TRIANGLE) {
vertices_[0] = A;
vertices_[1] = B;
vertices_[2] = C;
calculatePerimeter();
calculateArea();
}
// No Need To Do The Set & Get Colors - Base Class Does that for you.
// Methods that this shape must implement
virtual double calculateArea() override {
// Calculations For Getting Area of A Triangle
area_ = /* calculation */;
};
virtual double calculatePerimeter() override {
// Calculations For Getting Perimeter of A Triangle
perimeter_ = /* calculation */;
};
void modify<Triangle>( Triangle* pTriangle, glm::vec3, glm::vec3, glm::vec3, glm::vec4 = glm::vec4() ) override { /* ... */ }
};至于显示信息,我个人不会在这些类中实现这一点。只需使用您的标准std::cout或std::ofstream等将值打印到屏幕或文件,只需使用getters购买,如下所示:
#include <iostream>
#include "Triangle.h"
int main() {
Triangle t1( glm::vec3( 0.0f, 1.0f, -1.3f ), // Vertex A
glm::vec3( 3.2f, 5.5f, -8.9f ), // B
glm::vec3( -4.5f, 7.6f, 8.2f ), // C
glm::vec4( 0.8f, 0.9f, 0.23f, 1.0f ) ); // Color
std::cout << "Perimeter is " << t1.getPerimeter() << std::endl;
std::cout << "Area is " << t1.getArea() << std::endl;
return 0;
}发布于 2020-11-05 05:29:01
如果需要实现modifyShape函数,现在就执行
...这个函数在基类中应该是什么样子的?
这个函数应该是什么样子是一个见仁见智的问题,但让我们通过以下方式来解决这个问题:
C++ Core Guidelines通常被称为“最佳实践”指南,它建议使用preferring concrete regular types。我们可以使用该指导来解决问题,并提供此功能和设计可能看起来的方式。
首先,要了解多态类型和多态行为之间存在差异。
多态类型是至少具有或继承一个虚函数的类型。这个shape类和它的虚拟displayArea成员函数就是这样一个多态类型。在C++术语中,这些都是std:: is_polymorphic_v<T>返回true的T类型。
在这个问题上,多态类型与非多态类型的区别如下:
它们需要通过引用或指针来处理,以避免slicing.
int.这样的基本C++类型那样对待它们
因此,下面的代码将不适用于您提供的设计,但指导意见是它确实有效:
auto myShape = shape{triangle{1.0, 2.0, 2.0}}; // triangle data is sliced off
myShape.displayArea(); // UB: invalid memory access in displayArea
myShape = circle(4); // now circle data is sliced off from myShape
myShape.displayArea(); // UB: also invalid memory access is 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>;
// Example of how to modify a shape
auto myShape = shape{triangle{1.0, 2.0, 2.0}};
myShape = triangle{3.0, 3.0, 3.0};并且可以编写shape访问函数来调用适当的displayArea。
虽然这样的解决方案更有规律,但在分配给其他类型的形状时,使用std::variant是不开放的(除了为其定义的形状),并且像myShape = rectangle{1.5, 2.0};这样的代码将无法工作。
我们可以使用std::any而不是std::variant。这将避免仅支持定义的形状的缺点,就像使用std::variant一样。然后,使用此shape的代码可能如下所示:
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在他的talk、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;
// add pure virtual functions for any more functionality required for eligible shapes
};
// Model enforces functionality requirements for eligible types.
template <typename T>
struct Model final: Concept {
Model(T arg): data{std::move(arg)} {}
void displayArea_() const override {
displayArea(data);
}
// add overrides of any other virtual functions added to Concept
T data;
};
std::shared_ptr<const Concept> m_self; // Like a PIMPL
};
struct circle {
int radius = 0;
};
// Function & signature required for circle to be eligible instance for shape
void displayArea(const circle& value) {
// code for displaying the circle
}
struct triangle {
double a,b,c;
};
// Function & signature required for triangle to be eligible instance for shape
void displayArea(const triangle& value) {
// code for displaying the triangle
}
// Now we get usage like previously recommended...
auto myShape = shape{triangle{1.0, 2.0, 2.0}}; // triangle data is saved
displayArea(myShape); // calls displayArea(const triangle&)
myShape = circle{4}; // now circle data is stored in myShape
displayArea(myShape); // now calls displayArea(const circle&)
// And changing the settings like a modifyShape function occurs now more regularly
// by using the assignment operator instead of another function name...
mShape = circle{5}; // modifies shape from a circle of radius 4 to radius 5 这是这段代码的a link,它显示了代码的编译过程,并且这段shape也是一个具有多态行为的非多态类型。
虽然这项技术在机制方面带来了负担,但仍有一些努力可以使其变得更容易(如P0201R2)。此外,对于已经熟悉PIMPL习惯用法的程序员来说,我不会说这很难接受,因为从引用语义和继承的角度思考,到从术语值语义和组合的角度思考。
https://stackoverflow.com/questions/44666561
复制相似问题