Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Thoughts on this Style of Programming?

This thread is locked; no one can reply to it. rss feed Print
Thoughts on this Style of Programming?
Felix-The-Ghost
Member #9,729
April 2008
avatar

video

I want to get some more perspectives on this; I expect different opinions from you all. I'm not so comfortable talking on the subject to make a strong case either way.

Everyone's gotta do it the way they seem is right, I just wanted to warn this person of the potential obstacles this might create as the game/codebase expands. Maybe after all this method has good trade-offs.

==========================
<--- The ghost with the most!
---------------------------
[Website] [Youtube]

bamccaig
Member #7,536
July 2006
avatar

What he's doing is still object-oriented, but is not polymorphic. Ultimately, it's a fine strategy for some types of software. Note that you need access to the original source to make changes. You could not take somebody else's library designed this way and extend it easily, at least not where the library itself operated on the objects (unless you used function pointers instead of methods, but then you're probably reinventing the square wheel). I also think it loses a lot credibility when it comes to GUI widgets since again it would make it quite difficult to extend the GUI with new widgets unless you had the source. It does offer a potentially simple solution to a lot of problems, and probably the most valuable part is that the simplicity would encourage less over-engineering, whereas the full-on polymorphic graph of types tends to go overboard for no good reason. I don't think it's a silver bullet. The OP seems to be relatively early in his C++ growth, even though his game looks quite productive and polished.

Polybios
Member #12,293
October 2010

Normally, I don't watch linked videos at all. I made an exception and it was not worth it.

Don't get me wrong, normally, I'm all for "no limits for the programmer who knows what he's doing", especially if it's your personal project. But this seems to be overdoing it even for my generous tastes.

a) "Vectors"
Why reinvent the wheel with his arrays when he could just use std::vector?

He said it was for educational reasons and "that is was a really interesting way to do it".
Well, it probably is if you never did something like that, but I'd rather not recommend it as a general technique.

Seems like he learned OOP first and now discovers low level stuff. For me, it has been just the other way round, I did much C-only programming before moving on to C++. The one thing I've learned to appreciate very quickly were C++'s STL container classes. They're not perfect, but standardized and generic.
If there's a valid reason not to use them, you can still do so, but generally avoiding them just seems to ensure you don't get the benefits.
I also notice he's using C-strings there which ... could be more easy to manage (I wonder if he even frees them appropriately?).

b) Unions instead of inheritance
Advocating the use of unions this way just feels very wrong to me and I generally wouldn't recommend it to anyone except you have very good reasons to (e.g. wanting to have objects with the same size (again, for a good reason)). As long as the values are of the same type, it is less dangerous, but you basically loose type safety and can only use it for POD types. That's not very flexible and can be outright dangerous.

"No need to cast between derived types"? I cannot imagine a sensible situation where you would want your rectangle (l, w) be "reinterpreted" as a triangle (b, h). If I don't overlook something here, it's just a non-argument. Again, you loose type safety. I'd say it's nice to know for you, as a programmer, what your dealing with by being able to read your code semantically. You seem to loose that as well.

Note that with all that POD-stuff and manual memory-management, you further loose the ability to have (sensible default) c'tors and d'tors. It's a very convenient C++ feature that you can create objects as values on the stack and their c'tors and d'tors are called appropriately (when going out of scope).

c) enums instead of dynamic_cast
Who said the only alternative to an enum was dynamic_cast?
You can static_cast pointers and references just fine if you know what type you have (for example, by using an enum to encode types...)
You certainly do not need the union part for that.

Besides, especially on today's hardware, virtual function calls are certainly not the first thing to worry about.

d) Whole GUI done like that
Well, as bambam has already pointed out, I don't think it's very extensible (or readable).

Apart from that, I don't think OOP is the general answer either. Just try to use the right tool for the job. I'd say a GUI is one of those areas where OOP seems to make sense?

Generally, learning and experimentation are always a good thing, though. I'm personally not so convinced of making all of this process public.

Edit:
This reminds me of the day I talked to a new programmer who had mostly learned Java. He was in the process of familiarizing himself with C++ and somehow seemed to be very enthusiastic about "what you can do with void* pointers". Not everyone shared his enthusiasm. :)

Chris Katko
Member #1,881
January 2002
avatar

Polybios said:

b) Unions instead of inheritance

Advocating the use of unions this way just feels very wrong to me and I generally wouldn't recommend it to anyone except you have very good reasons to (e.g. wanting to have objects with the same size (again, for a good reason)). As long as the values are of the same type, it is less dangerous, but you basically loose type safety and can only use it for POD types. That's not very flexible and can be outright dangerous.

I can't watch the video yet, I'm at work at the moment but...

Isn't this how the ALLEGRO_EVENT structure is set up?

https://www.allegro.cc/manual/5/ALLEGRO_EVENT

Quote:

An ALLEGRO_EVENT is a union of all builtin event structures, i.e. it is an object large enough to hold the data of any event type. All events have the following fields in common:

And again, I didn't see the video yet but any time I see someone say "Use this instead of inheritance" my first thought is, "Why not composition?"

-----sig:
“Programs should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

DanielH
Member #934
January 2001
avatar

I prefer polymorphism over his unions.

What about a simple get_area function? Creating a separate sub-class for each shape, would take longer but be cleaner and easier to handle.

class Rectangle : public Shape
{
..
float w, h;

float get_area() { return w*h; }
..
};

With unions, it makes for one big function.

float get_area()
{
// some switch statement or multiple ifs
if (type == RECTANGLE) return {w*h};

If you happen to add a shape, you'ld have to go through and modify each function. Messy.

Polybios
Member #12,293
October 2010

You could do something like that to emulate virtual functions...

#SelectExpand
1#include <cstdio> 2 3struct ShapeVTable; 4 5struct Shape { 6 float x, y; 7 char* name; 8 struct ShapeVTable* vt; 9 enum Type { Rectangle, Triangle }; 10 11 union { 12 /* RECTANGLE DATA */ 13 struct { 14 float l, w; 15 } rect; 16 17 /* TRIANGLE DATA */ 18 struct { 19 float b, h; 20 } tri; 21 }; 22}; 23 24struct ShapeVTable { 25 Shape::Type type; 26 float (*area) (Shape& s); 27}; 28 29 30struct ShapeVTable rectVTable { 31 .type = Shape::Type::Rectangle, 32 .area = [](Shape& s) { return s.rect.l * s.rect.w; } 33}; 34 35struct ShapeVTable triVTable { 36 .type = Shape::Type::Triangle, 37 .area = [](Shape& s) { return 0.5f*s.tri.b * s.tri.h; } 38}; 39 40 41int main() { 42 Shape rectangle { 0.0, 0.0, nullptr, &rectVTable, { 2.0, 2.0 } }; 43 Shape triangle { 0.0, 0.0, nullptr, &triVTable, { 2.0, 2.0 } }; 44 45 printf("My type is %d, my area is: %f\n", rectangle.vt->type, 46 rectangle.vt->area(rectangle)); 47 printf("My type is %d, my area is: %f\n", triangle.vt->type, 48 triangle.vt->area(triangle)); 49}

... but it would be much easier to just use the language features. :)

Basically, triangle.vt->area(triangle) is a virtual function call, now you see why they are so slow. >:(

ALLEGRO_EVENT probably needs to be the same size for all sub-types, so that it fits into a C container. That's a valid reason to use this technique. Plus, Allegro is C only anyway.

Audric
Member #907
January 2001

The specific advantage of union is that it allows you to change the type of an instance at runtime : Allocated elements always take the same amount of memory, so you don't need to reallocate, it means you don't have to track down every pointer to the element to update them.

bamccaig
Member #7,536
July 2006
avatar

That assumes that the data is compatible. Otherwise, you still would need to reinitialize it, effectively "constructor" it (and possibly destroy() it first if it contains any heap allocated pointers). The only thing you save is system-level memory allocations because you effectively have a pool. But that could be managed with a raw chunk of allocated memory too if you thought that you could write your own memory allocator that was faster than malloc/free. I think the only valid advantage here is that the objects are all the same size so it's easier to process the pool as a pipeline: it's just an array of fixed size elements instead of a block of self-managed memory.

In particular, you can see where this would be useful in a game with a large, but limited set of entities that can spawn and be destroyed dynamically. You can end up with a pool of fixed size entities to use and process. I think that a lot of this breaks down when you get around to business software (i.e., the majority of software that there is or ever will be), and I think that a lot of people neglect to mention that disclaimer when they're boasting about these "better" design patterns.

Unions are great for some purposes. For example, the concept of a "variant" type or universal type is made possible by this concept. While they have their limitations, they also have their uses. You'll likely find one at the core of every dynamic language there is or ever will be. :)

Polybios
Member #12,293
October 2010

AFAIK, there will be type-safe std::variants in C++ 17.

devo_au
Member #15,282
August 2013

Watched the vid. Always enjoy watching punters passionate about their projects. Everyone has their preferred way of doing things. Some people prefer procedural, and some OOP. Im a bit in between myself. I agree with poly about his memory management. Why con volute your project with an algorithm when you can do it with a vector or a list? This guy wants to go back to procedural after the OOP, why not... It's not lower level. It's just a style thing. Any performance increase wouldn't be noticeable or worth the effort.I don't think it will harm his project any. For a project like his anyway. Good luck..

it wasn't me, it was other kids

Go to: