|
Thoughts on this Style of Programming? |
Felix-The-Ghost
Member #9,729
April 2008
|
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. |
bamccaig
Member #7,536
July 2006
|
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. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
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" He said it was for educational reasons and "that is was a really interesting way to do it". 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. b) Unions instead of inheritance "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 Besides, especially on today's hardware, virtual function calls are certainly not the first thing to worry about. d) Whole GUI done like that 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: |
Chris Katko
Member #1,881
January 2002
|
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: |
DanielH
Member #934
January 2001
|
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... 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
|
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. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
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 |
|