【问题标题】:Correct use of polymorphism in containers?在容器中正确使用多态性?
【发布时间】:2016-06-23 07:37:22
【问题描述】:

在我开始编写的这段代码中,我遇到了一个不适合我的常见模式。通常,它涉及枚举、映射、开关和某种类层次结构。我试图抽象出一个 MWE:

#include <iostream>
#include <map>

class Shape {
public:
    virtual double SumOfInternalAngles() { throw std::exception(); }
};

class Triangle : public Shape {
public:
    double SumOfInternalAngles() { return 180.0; }
};

class Rectangle : public Shape {
public:
    double SumOfInternalAngles() { return 360.0; }
};

enum TeamShapes {AlicesTriangle, BobsRectangle, CarolsTriangle};

int main()
{
    Triangle alicesTriangle;
    Rectangle bobsRectangle;
    Triangle carolsTriangle;

    std::map<TeamShapes, Shape*> shapeMap;
    shapeMap[TeamShapes::AlicesTriangle] = &alicesTriangle;
    shapeMap[TeamShapes::BobsRectangle] = &bobsRectangle;
    shapeMap[TeamShapes::CarolsTriangle] = &carolsTriangle;

    for(auto it : shapeMap)
    {
        switch (it.first)
        {
        case TeamShapes::AlicesTriangle:
            std::cout << it.second->SumOfInternalAngles() << std::endl;
            break;

        case TeamShapes::BobsRectangle:
            std::cout << static_cast<Rectangle*>(it.second)->SumOfInternalAngles() << std::endl;
            break;
        }
    }
    return 0;
}

似乎有重复的信息,访问成员函数的两个版本都有缺点:第一种情况,你需要在基类中有一个虚成员函数,这意味着所有的派生类都变得“杂乱无章”具有对他们没有真正意义的功能,例如Circle 会以函数 getCorners() 结束。在第二种情况下,我宁愿不需要演员表,尽管我知道这是必要的。也许有人可以指出我可以为这种情况提出更好设计的方向。

我对 C++ 很陌生,所以我想听听关于此类构造的“最佳实践”和“约定”是什么。也许代码没问题,我只需要调整?

【问题讨论】:

  • for(auto it 复制映射中的键值对。在这里无关紧要,但通常你会想要for(auto &amp;it
  • 为什么你认为在你的例子中演员是必要的?不是。
  • 我知道这没有必要(我使用了没有3行以上的版本),但我不希望在基类中有SumOfInternalAngles,然后它会是必要的。
  • 不要将相关的类型放在同一个容器中,然后区别对待(通过尝试访问派生类方法)。如果您需要以不同的方式对待您的对象,请考虑将它们保存在separate containers
  • 是的,这可能正是困扰我的地方。 Shape的map要使用多态,然后枚举和switch做相反的事情。

标签: c++ enums polymorphism containers


【解决方案1】:

如果您将基类设为纯虚拟(即使类抽象),那么您不必在其中实现SumOfInternalAngles(),因此无需抛出异常。然后 Shape 成为一个抽象接口,由派生类实现,并且这个派生类必须实现 SumOfInternalAngles()

然后在你的 switch 语句中,你不需要强制转换,除非你想调用一个特定于派生类的方法,例如 getCorners(),它可能出现在 Shape 的所有派生版本中,也可能不出现。

要做到这一点,只需将形状定义更改为

class Shape {
public:
    virtual double SumOfInternalAngles() = 0;
};

并使用您的开关盒的第一个版本。

 case TeamShapes::AlicesTriangle:
         std::cout << it.second->SumOfInternalAngles() << std::endl;
         break;

 case TeamShapes::BobsRectangle:
         std::cout << it.second->SumOfInternalAngles() << std::endl;
         break;

编辑:一些示例代码尝试帮助说明。

#include <iostream>
#include <string>

class base
{
public:

    virtual std::string AMethodThatMustBeImplemented() = 0;
    virtual std::string ABaseMethod() { return std::string("base::ABaseMethod"); }
};

class A : public base
{
public:
    virtual std::string AMethodThatMustBeImplemented() { return std::string("A::AMethodThatMustBeImplmented"); }
    // No need to implment ABaseMethod here unless we wanted to!
};

class B : public base
{
public:
    virtual std::string AMethodThatMustBeImplemented() { return std::string("B::AMethodThatMustBeImplmented"); }
    virtual std::string ABaseMethod() { return std::string("B::ABaseMethod"); }
};

int main(int argc, char** argv)
{
    //base obj;     // can't do this since base has 'pure virtual' called AMethodThatMustBeImplemented.

    A objA;
    B objB;

    std::cout << objA.AMethodThatMustBeImplemented() << '\n';
    std::cout << objA.ABaseMethod() << '\n';
    std::cout << objB.AMethodThatMustBeImplemented() << '\n';
    std::cout << objB.ABaseMethod() << '\n';

    base& b = static_cast<base&>(objB);
    std::cout << b.ABaseMethod() << "   <- notice still calling B::ABaseMethod\n";
    std::cout << b.base::ABaseMethod() << "    <- ah-ha now calling base::ABaseMethod\n";

}

【讨论】:

  • 好的,谢谢。但是像getCorners 这样的任何“特定”方法仍然需要强制转换或异常,因为我不想强制Circle 实现它,对吧?
  • 你有两个选择......要么调用者知道一个类型的细节(在本例中为Circle),因此转换为该类型并调用getCorners(),它只在类@中定义987654332@ 或getCorners()Shape 接口的一部分,您实现getCorners() { throw Exception("An unspecified shape has no concept of corners"); } 的方式与最初为SumOfInternalAngles() 执行的方式相同。这意味着getCorners()只需要在你想要的派生类中实现(不需要为每个类声明重复语法)。
  • 如果调用者调用getCorners() 并且类有它(即它的类型为CircleCircle::getCorners() 将被调用。如果派生类没有getCorners(),则将调用基方法(Shape::getCorners())。简而言之,如果声明为纯虚拟(即末尾带有 '=0'),它必须在 dervied 中实现。如果它只是“虚拟”(并在基类中实现),则不必在派生中实现。
【解决方案2】:

也许您想要的是派生类的中间接口,在本例中是从“Shape”派生的“Polygon”接口,它具有对多边形有意义的方法,但不适用于圆形:

#include <iostream>
#include <map>
#include <math.h>

class IShape {
public:
    virtual double getArea() = 0;
};

class IPolygon : public IShape {
public:
    virtual double sumOfInternalAngles() = 0;
};


class Triangle : public IPolygon {
public:
    Triangle(double _base, double _height) : base(_base), height(_height) {}

    double sumOfInternalAngles() { return 180.0; }
    double getArea() { return (base * height)/2; }

private:
    double base;
    double height;
};

class Rectangle : public IPolygon {
public:
    Rectangle(double _width, double _height) : width(_width), height(_height) {}

    double sumOfInternalAngles() { return 360.0; }
    double getArea() { return (width * height); }

public:
    double width;
    double height;
};

class Circle : public IShape {
public:
    Circle(double _radius) : radius(_radius) {}

    double getArea() { return (3.14 * pow(radius,3)); }

public:
    double radius;
};


enum TeamShapes {AlicesTriangle, BobsRectangle, CarolsCircle};

int main()
{
    Triangle alicesTriangle(10,10);
    Rectangle bobsRectangle(10,10);
    Circle carolsCircle(10);

    std::map<TeamShapes, IShape*> shapeMap;
    shapeMap[TeamShapes::AlicesTriangle] = &alicesTriangle;
    shapeMap[TeamShapes::BobsRectangle] = &bobsRectangle;
    shapeMap[TeamShapes::CarolsCircle] = &carolsCircle;

    for(const auto& it : shapeMap)
    {
        switch (it.first)
        {
        case TeamShapes::AlicesTriangle:
        case TeamShapes::BobsRectangle:
            std::cout << static_cast<IPolygon*>(it.second)->sumOfInternalAngles() << std::endl;
            std::cout << it.second->getArea() << std::endl;
            break;

        case TeamShapes::CarolsCircle:
            std::cout << it.second->getArea() << std::endl;
            break;
        }
    }
    return 0;
}

你仍然需要转换你的指针,但我发现它比抛出异常的非虚拟方法更可取。

【讨论】:

    猜你喜欢
    • 2012-04-20
    • 2016-04-09
    • 2016-10-16
    • 2020-03-01
    • 2020-10-21
    • 2013-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多