14

I'm an embedded software engineer and coming from the world of bits and C. In that world, there are data in flash memory represented by const in C. And there are data in RAM. RAMs are expensive and limited, while flash memory is cheap and enough. Also, dynamic memory allocation using new, delete, malloc etc is not allowed due to fragmentation problem or safety regulations, static designs are preferred.

I've around 2000 objects which have similar constant properties but different behaviors. So for them, I defined Shape Class as a base class which holds shared properties of my objects. And to represent different behavior, Shape Class has one abstract method called Print() which will be overwritten by parents.

ShapeList is the important part. It is a const array consists of "const Shapes" so that they will be placed into flash memory section by linker.

Below program produces an output:

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

While expected output is:

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

I need polymorphic behavior. When I print Triangle, it should behave like a Triangle, not as a Shape. How can I do this?

Thanks.

#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);
}

More problem: Today I realized that there is another problem with virtual functions. When I add any virtual functions into base class, compiler start ignoring "const" directive and place object automatically to the RAM instead of flash memory. I don't know why. I've asked this question to IAR. The conclusion I got so far is that Polymorphic behavior is not possible with ROMable classes even with heap :/

Mehmet Fide
  • 1,643
  • 1
  • 20
  • 35
  • 10
    They need to be pointers otherwise you get object slicing http://en.wikipedia.org/wiki/Object_slicing – gvd Dec 24 '13 at 17:10
  • possible duplicate of [Array of polymorphic base class objects initialized with child class objects](http://stackoverflow.com/questions/7203590/array-of-polymorphic-base-class-objects-initialized-with-child-class-objects) – juanchopanza Dec 24 '13 at 17:11
  • I didn't read the question closely enough. `Also, dynamic memory allocation using new, delete, malloc etc is not allowed due to fragmentation problem or safety regulations, static designs are preferred.` From what I've read, it is not possible to achieve polymorphism without pointers or references. –  Dec 24 '13 at 17:17
  • @juanchopanza: No solution to my question there. – Mehmet Fide Dec 24 '13 at 17:18
  • @remyabel: This is interesting. When I instantiate objects statically, that doesn't mean that they don't have address. They have all addresses. Why do I need new or malloc? – Mehmet Fide Dec 24 '13 at 17:22
  • I telly you why you cannot use the run-time polymorphism based on inheritance. There may be other approaches, but any answer would depends on more details. For example, do your types have state? Do the derived types have different member variables? – juanchopanza Dec 24 '13 at 17:23
  • 3
    Note that you don't necessarily need `new` or `malloc`. You just need to store pointers in an array. Whether the objects they point to are dynamically allocated or not is an orthogonal issue. – juanchopanza Dec 24 '13 at 17:24
  • @juanchopanza: Yes some parents may have extra properties which are not available in base class. Is it a problem? Also can you give me an example how to store their addresses in an array? But that array has to be constant as well. – Mehmet Fide Dec 24 '13 at 17:30
  • what about a delegate method or function pointer to the baseclass to avoid polymorphism? – Diversity Dec 24 '13 at 17:36
  • Can't you declare an array of function pointers FuncArr. Then create an enum type called ShapeType and each ShapeType will correspond to the index of its print function pointer located in the array FuncArr. This way each derived class will hav a FuncId/ShapeType that can be used to both identify the Shape and to use its corresponding Print function. – Valentin Dec 24 '13 at 17:44
  • how about array of chars (buffer) and placement new for allocation of objects? – UldisK Dec 24 '13 at 18:00

5 Answers5

11

This version doesn't use dynamic memory:

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();

In languages like C#, you can use the as keyword to achieve "polymorphism". In C++, it looks something like this:

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

If the pointer returned by dynamic_cast is valid, you can call Triangle's special function. This for example will allow you to have a loop that iterates over ShapeList and call only Triangle methods. If you can use exceptions, consider wrapping it in a try catch block and catching std::bad_cast.

Note: you need a const pointer because ShapeList[i] is const. The reason the static_cast is necessary is because you're calling a non-const method on a const pointer. You can add the const qualifier, like SomethingSpecial() const and then just do tri->SomethingSpecial(). Otherwise, you just cast the const off.

For example:

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

This will work:

const_cast<Triangle*>(tri)->SomethingSpecial();
  • @ChristianSchack What would be the benefit? Especially when the subclasses all need different additional data. –  Dec 24 '13 at 17:36
  • Himm this solutions looks good to me, at least better than other solutions. May I ask why that crashes: "const std::array ShapeList = { &Triangle(), &Rectangle(), &Pentagon() };" ? – Mehmet Fide Dec 24 '13 at 17:42
  • op said that data is the same to all subclasses . So the arguments of the functions wouldnt change. just the behaviour. – Diversity Dec 24 '13 at 17:43
  • 1
    @Mehmet, that crashes because you are taking the address of a hidden temporary in this case. The temporary variable goes out of scope, leaving the behavior undefined. Probably the temp was allocated on a stack, which gets overwritten later on during execution. – Mike Woolf Dec 24 '13 at 17:44
  • Just a note that the static_cast is unnecessary, since tri is already of type Triangle*. – Mike Woolf Dec 24 '13 at 17:50
  • @Mike I wrote an explanation. –  Dec 24 '13 at 17:54
  • @MikeWoolf: (re: your first comment) That would be the case, if the code even compiled. It shouldn't compile though, as you can't take the address of a temporary. Some compilers (msvc) may allow it as an extension though. – Benjamin Lindley Dec 24 '13 at 17:55
  • @remyabel: tri, rect and pent have to be const otherwise they will be instantiated on run-time and placed into ram instead of flash memory. – Mehmet Fide Dec 24 '13 at 20:26
10

As others have pointed out, the convenient and common way doesn't work like that. Fixing it results in code which is at odds with the restrictions of your target platform. You can, however, emulate polymorphism differently in several different ways.

You can segregate the objects by type like this:

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], ...}:

You still need to make all methods (that behave differently for the various types) virtual, and you pay an extra pointer (two if you count the vtable pointer, which would be a bit unfair) per object. You could also remove everything virtual in favor of an explicit tag:

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

Use a union if the various subclasses need very different member data. This has numerous severe restrictions and leads to rather ugly code, but can work well. For example, you need know your hierarchy up front and the subclasses need to be roughly the same size. Note that this is not necessarily faster than the virtual alternative, but when it's applicable it can take less space (a byte instead of a vtable pointer) and make introspection leaner.

Community
  • 1
  • 1
  • 1
    "Note that this is not necessarily faster" - true, but when it is it can be *much* faster for trivial functions that get inlined (up to around an order of magnitude depending on compiler/CPU etc).... – Tony Delroy Dec 24 '13 at 18:16
  • C++17 [`std::variant`](http://en.cppreference.com/w/cpp/utility/variant) gives you a type-safe union. You could maybe even use `std::variant` and use [`std::visit` (comes with `variant`)](http://en.cppreference.com/w/cpp/utility/variant/visit) to dispatch based on the right object type. This should give you about the same functionality (and runtime performance) as `enum ShapeKind` + manual dispatching. – Peter Cordes Oct 06 '17 at 03:31
9

You can use polymorphism along with all of your constraints with a minor change to your code:

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

const std::array<const Shape*, 3> ShapeList = { &triangle, &rectangle, &pentagon };
Mike Woolf
  • 1,210
  • 7
  • 11
5

Any easy fix would be to add a string the shape which defines what type of shape it is.

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") { }
};
Jesse Laning
  • 490
  • 4
  • 12
  • good thought. it's better to save the name field in the base class since all use it. – Itzik984 Dec 24 '13 at 17:31
  • this is not good for me. Shape::Print() function will grow like crazy to support all behaviors of parents. Also all team members has to modify Shape::Print() function each time when they define a new shape has different behavior. – Mehmet Fide Dec 24 '13 at 17:35
  • http://www.cplusplus.com/doc/tutorial/polymorphism/ well your code looks like it should work, look through this link though. Sorry my answer didn't help with your situation. – Jesse Laning Dec 24 '13 at 18:24
  • @MehmetFide why would it go crazy? it will use the virtual table anyway in order to eventually get the name so isn't the same basicly? – Itzik984 Dec 24 '13 at 18:43
  • @Itzik984: Yes but virtual table will be under compiler responsibility. In the solution offered here, Shape::Print method most probably has to contain huge switch-case to implement all parents behavior depends on shapeType. This is also not a good approach if you are a team and lots of programmers work on it. Imagine that, you are going to implement Triangle, I'll do rectangle and someone else does more. Each of us has to agree for the shapeType and everybody has to extend Shape::Print method to support his shape behavior. With a real polymorphic solution, everybody will be independent. – Mehmet Fide Dec 24 '13 at 19:23
  • @MehmetFide i see you chose the other answer and it will work, and in a good way. i still think that no matter what, you will use the virtual table in order to get to the relevant print() method, so no extra switch cases will be used. the same thing goes for the dots field. both are different per object, but all objects hold these fields. anyway this is just for the argument's sake. would love to read your opinion if you are into it :) – Itzik984 Dec 24 '13 at 19:52
  • `printf` has a `%s` format for `const char*`. That would be way better than using 3 calls to printf and introducing a possible format-string vulnerability (if `shapeType` points to a string containing a `%`). – Peter Cordes Oct 06 '17 at 03:45
  • More importantly, this answer is just a roundabout way of saying "don't use polymorphism at all". Obviously it's easy for `Print()`, but it could certainly be harder for a method like `Area()` (and would end up with a `switch()` inside functions instead of vtable dispatch). – Peter Cordes Oct 06 '17 at 03:46
3

Another solution I have found in C for polymorphic dynamic dispatch without dynamic allocation is to pass vtable pointers manually, like GHC’s desugaring of typeclasses in Haskell. This approach would also be reasonable in C++, because it’s lightweight and strictly more general than what C++’s object system allows.

An overloaded/polymorphic function takes a pointer to a struct of function pointers for each typeclass to which a parameter’s type belongs—equality comparison, ordering, &c. So you might have:

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;

}

Since the type parameters are “phantom types” not used representationally, the linker should be able to deduplicate this kind of function, if you’re worried about code size. The type-unsafe but perhaps more compiler-friendly alternative would be to use void*.

In C++, you can also pass the vtable functions as template parameters if you know them at compile time—that is, manual devirtualisation. This admits more optimisations (e.g., inlining) but obviously doesn’t allow dynamic dispatch.

One caveat: since you don’t have partial function application or closures, you will find it an interesting experience to make partial specialisations, such as the Haskell:

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

Which says that a list of things [a] has an ordering if the elements a have an ordering.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166