数组中对象的C ++多态性

时间:2013-12-24 17:08:46

标签: c++ arrays polymorphism

我是一名嵌入式软件工程师,来自于C和C世界。 在那个世界中,闪存中有数据由C表示为const。并且RAM中有数据。 RAM昂贵且有限,而闪存便宜且足够。此外,由于碎片问题或安全规定,不允许使用new,delete,malloc等动态内存分配,首选静态设计。

我有大约2000个具有相似常量属性但行为不同的对象。 所以对于他们来说,我将Shape Class定义为一个基类,它保存了我的对象的共享属性。为了表示不同的行为,Shape Class有一个名为Print()的抽象方法,它将被父母覆盖。

ShapeList是重要的部分。它是一个const数组,由“const Shapes”组成,因此它们将被链接器放入闪存部分。

以下程序产生一个输出:

I'm a Shape has 3 dots
I'm a Shape has 4 dots
I'm a Shape has 5 dots

预期输出为:

I'm a Triangle has 3 dots
I'm a Rectangle has 4 dots
I'm a Pentagon has 5 dots

我需要多态行为。当我打印三角形时,它应该像三角形,而不是形状。我怎么能这样做?

感谢。

#include <array>
#include <cstdio>
class Shape
{
    public:
    const int DotCount;
    Shape(const int dot): DotCount(dot) {}
    virtual void Print(void) const; // this is virtual method
};

void Shape::Print(void) const
{
    printf("I'm a Shape has %d dots\n", DotCount);
}

class Triangle: public Shape
{
    public:
    Triangle(void): Shape(3) { }
    void Print(void) const;
};

void Triangle::Print(void) const
{
    printf("I'm a Triangle has %d dots\n", DotCount);
}

class Rectangle: public Shape
{
    public:
    Rectangle(void): Shape(4) { }
    void Print(void) const;
};

void Rectangle::Print(void) const
{
    printf("I'm a Rectangle has %d dots\n", DotCount);
}

class Pentagon: public Shape
{
    public:
    Pentagon(void): Shape(5) { }
    void Print(void) const;
};

void Pentagon::Print(void) const
{
    printf("I'm a Pentagon has %d dots\n", DotCount);
}

const std::array<const Shape, 3> ShapeList = { Triangle(), Rectangle(), Pentagon() };

int main(void)
{
    ShapeList.at(0).Print();
    ShapeList.at(1).Print();
    ShapeList.at(2).Print();
    return(0);
}

更多问题: 今天我意识到虚拟功能还有另一个问题。当我将任何虚函数添加到基类中时,编译器开始忽略“const”指令并将对象自动放入RAM而不是闪存。我不知道为什么。我已经向IAR提出了这个问题。到目前为止我得出的结论是,即使使用堆:/

,ROMable类也无法实现多态行为

5 个答案:

答案 0 :(得分:10)

此版本不使用动态内存:

Triangle tri;
Rectangle rect;
Pentagon pent;
const std::array<const Shape*, 3> ShapeList {
    &tri, &rect, &pent
};
for (unsigned int i = 0; i < ShapeList.size(); i++)
    ShapeList[i]->Print();

在C#等语言中,您可以使用as关键字来实现“多态”。在C ++中,它看起来像这样:

    const Triangle* tri = dynamic_cast<const Triangle*>(ShapeList[i]);
    if (tri)
        static_cast<Triangle>(*tri).SomethingSpecial();

如果dynamic_cast返回的指针有效,则可以调用Triangle的特殊功能。例如,这将允许您有一个迭代ShapeList并仅调用Triangle方法的循环。如果您可以使用例外,请考虑将其包含在try catch块中并抓取std::bad_cast

注意:您需要一个const指针,因为ShapeList[i]是const。 static_cast是必要的原因是因为你在const指针上调用非const方法。您可以添加const限定符,例如SomethingSpecial() const,然后执行tri->SomethingSpecial()。否则,您只需关闭const

例如:

static_cast<Triangle*>(tri)->SomethingSpecial();
// error: static_cast from type 'const Triangle*' to type 'Triangle*' 
// casts away qualifiers

这将有效:

const_cast<Triangle*>(tri)->SomethingSpecial();

答案 1 :(得分:9)

正如其他人所指出的那样,方便和通用的方式并不像那样。修复它会导致代码与目标平台的限制不一致。但是,您可以通过几种不同的方式模拟多态性。

您可以按类型隔离对象:

const Triangle tris[] = {tri1, tri2, ...};
const Rectangle rects[] = {rect1, rect2, ...};
// for correct order, if needed
const Shape * const shapes[] = {&tris[0], &rects[2], &rects[0], ...}:

你仍然需要制作所有方法(对于各种类型的行为不同)virtual,并且你需要为每个对象支付额外的指针(如果计算vtable指针,则为两个,这有点不公平)。 您还可以删除所有virtual以支持显式标记:

enum ShapeKind { Triangle, Rectangle, Pentagon };
struct Shape {
    ShapeKind kind;
    int sides;
    ...
};

如果各种子类需要非常不同的成员数据,请使用union。 这有很多严格的限制,导致相当丑陋的代码,但可以很好地工作。例如,您需要预先了解您的层次结构,并且子类需要大致相同的大小。请注意,这不一定比virtual替代更快,但是当它适用时,它可以占用更少的空间(一个字节而不是一个vtable指针)和make introspection leaner

答案 2 :(得分:8)

您可以将多态性与所有约束一起使用,只需对代码进行微小更改:

const Triangle triangle;
const Rectangle rectangle;
const Pentagon pentagon;

const std::array<const Shape*, 3> ShapeList = { &triangle, &rectangle, &pentagon };

答案 3 :(得分:5)

任何简单的解决方法都是添加一个字符串,该字符串定义了它的形状类型。

class Shape
{
    public:
    const int DotCount;
    const char* shapeType
    Shape(const int dot, const char* type): DotCount(dot), shapeType(type) {}
    void Print(void) const;
};

void Shape::Print(void) const
{
    printf("I'm a "); printf(shapeType); printf(" has %d dots\n", DotCount);
}

class Triangle: public Shape
{
    public:
    Triangle(void): Shape(3, "Triangle") { }
};

答案 4 :(得分:3)

我在C中发现的另一种没有动态分配的多态动态调度的解决方案是手动传递vtable指针,就像GHC在Haskell中对类型类的贬低一样。这种方法在C ++中也是合理的,因为它比C ++的对象系统允许的更轻,更严格。

重载/多态函数获取指向参数类型所属的每个类型类的函数指针结构的指针 - 相等比较,排序和&amp; c。所以你可能有:

template<class Container, class Element>
struct Index {
  size_t (*size)(const Container& self);
  const Element& (*at)(const Container& self, size_t index);
};

enum Ordering { LT, EQ, GT };

template<class T>
struct Ord {
  Ordering (*compare)(const T& a, const T& b);
};

template<class Container, class Element>
const Element* maximum(
  const Index<Container, Element>& index,
  const Ord<Element>& ord,
  const Container& container) {

  const size_t size = index.size(container);
  const Element* max = nullptr;

  for (size_t i = 0; i < size; ++i) {
    const Element& current = index.at(container, i);
    if (!max || ord.compare(current, *max) == GT)
      max = &current;
  }

  return max;

}

由于类型参数是代表性未使用的“幻像类型”,如果您担心代码大小,链接器应该能够对这种函数进行重复数据删除。类型不安全但可能更适合编译器的替代方法是使用void*

在C ++中,如果您在编译时知道它们,也可以将vtable函数作为模板参数传递 - 即手动虚拟化。这允许更多的优化(例如,内联),但显然不允许动态调度。

一个警告:由于您没有部分功能应用程序或闭包,您会发现进行部分特化的有趣体验,例如Haskell:

instance (Ord a) => Ord [a] where ...

如果元素[a]有排序,那么a列表就会有排序。