Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » Class bloat - "How much is too much?"

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Class bloat - "How much is too much?"
DanielH
Member #934
January 2001
avatar

Background: I've been working on/off on a GUI library for years. I don't program very much anymore, so it's slow going. I was using std::string for text, but then I read/heard that the std::string class is very bloated. I decided then to switch to ALLEGRO_USTR for all text. While building a string class wrapper for ALLEGRO_USTR, I "borrowed" and modified the string class axilmar's Allegro C++ class wrapper.

However, I keep coming back to the concept of class bloat. His class is very bloated. I'm now thinking of making each ALLEGRO object into a minimal class.

#SelectExpand
1#include <allegro/allegro.h> 2#include <memory> 3 4template <class TYPE> 5static null_deleter(TYPE *) {} 6 7template <class TYPE, void (DELETER*)(T*)> 8class SharedObject 9{ 10public: 11 SharedObject() : m_object(nullptr) {} 12 13 SharedObject(const SharedObject &object) : m_object(object.m_object) {} 14 15 SharedObject(TYPE *ptr, bool managed= true) : m_object(ptr, (managed?DELETER:null_deleter<TYPE>)) {} 16 17 ~SharedObject() {} 18 19protected: 20 std::shared_ptr<TYPE> m_object; 21}; 22 23 24typedef typename SharedObject<ALLEGRO_USTR, al_ustr_free> AllegroString; 25typedef typename SharedObject<ALLEGRO_BITMAP, al_destroy_bitmap> AllegroBitmap;

This removes the bloat from the class, but then I would need "helper" classes to handle object manipulations.

Advice and Comments

1. Keep bloat and make life simpler

2. Remove bloat and make the code uglier

// example

// 1
AllegroString str;

str.assign("Hello World");

//2
AllegroString str;

StringHelper::assign(str, "Hello World!");

Edgar Reynaldo
Member #8,592
May 2007
avatar

DanielH said:

This removes the bloat from the class, but then I would need "helper" classes to handle object manipulations.

What's wrong with that? Although I don't think we're thinking of the same approach. Why don't you just derive concrete classes from your SharedObject class? That's what I do in my GUI. Everything that I want to be managed derives from EagleObject, and there's a registry that each object registers itself with. You can delete any object any time, and if you want to delete them all it's one function call. Simple cleanup, no leftovers. For my widgets, I have a factory that creates all widgets and registers them with the factory. You can give them a group name and delete widgets by group, or singly.

class SharedObjectBase {
   virtual ~SharedObjectBase(){}
};

template <class TYPE, void (DELETER*)(T*)> 
class SharedObject : public SharedObjectBase {/*...*/};

class AllegroString : public SharedObject<ALLEGRO_USTR , al_free_ustr> {
   /*...*/
};

That way you retain the automatic object clean up, but are allowed to expand your class as needed.

Mark Oates
Member #1,146
March 2001
avatar

DanielH said:

axilmar's Allegro C++ class wrapper

Oh wow! All his classes are header-only files. That's rather clever. :)

bamccaig
Member #7,536
July 2006
avatar

Bloat is subjective. What is the problem? Is it using up too much space? Does it use too much memory? Is it too slow? Ultimately, you have to pick and choose your battles. Do you want code that performs optimally or do you want code that is powerful and flexible and able to just do what you want without much fuss? You cannot have both. Even with performance you usually have to choose what kinds of performance to optimize for. At the end of the day, you shouldn't fear "bloat" just because somebody else thinks that's a problem. You should only care about bloat when it becomes a problem. The C++ standards people are smart people. They aren't going into the process ignorant and just doing whatever they can come up with. It's a group of people that have worked on the problem for years and come up with what they think is the best compromise for a standard. Each library and implementation will have its own defined purpose. There can never be a single solution that fits all problems. You need to figure out which solutions best solve your problems. If you don't know then take the easy route until that doesn't work anymore and you're forced to rethink it. Odds are you'll never reach that point of having to change your mind. But if you do you'll learn something and next time you'll know better.

Audric
Member #907
January 2001

DanielH said:

it's slow going.

If you're re-building base types, this is going to take a while...

Quote:

While building a string class wrapper for ALLEGRO_USTR

I'm not sure if I read that correctly. Does this involve the API of your library ? Does it mean the user who wants to place a label will need to learn how to use your string class first ? What if the user is ALREADY using ALLEGRO_USTR structs ? The user will have to convert each of them to your AllegroString class, pass it to your API, which will unwrap it back to an ALLEGRO_USTR...

Mark Oates
Member #1,146
March 2001
avatar

bamccaig said:

Ultimately, you have to pick and choose your battles. Do you want code that performs optimally or do you want code that is powerful and flexible and able to just do what you want without much fuss? You cannot have both.

In other words, DanielH, programming is like this:

{"name":"610565","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/0\/70e4cd81237272ed2c97a6f0560d86ea.gif","w":300,"h":225,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/0\/70e4cd81237272ed2c97a6f0560d86ea"}610565

Don't let it stress you out. :) KCACO.

Arvidsson
Member #4,603
May 2004

Also, build what you need right now and not what you might need in the future in order to avoid unnecessary "bloat".

bamccaig
Member #7,536
July 2006
avatar

Audric said:

What if the user is ALREADY using ALLEGRO_USTR structs ? The user will have to convert each of them to your AllegroString class, pass it to your API, which will unwrap it back to an ALLEGRO_USTR...

Ideally there would be a constructor overload that accepts an ALLEGRO_USTR and just assigns its internal pointer (or maybe copies the structure). It should be very fast and light-weight if the caller is using the same underlying types. It will just suck if the caller isn't. But that's always the problem with strings where performance matters (and if performance doesn't matter then it doesn't matter that you have to do various conversions anyway).

Audric
Member #907
January 2001

I wanted to comment further, but I checked again, and in the ALX wrapper referred, I don't see any form of "class bloat" as I imagined it : The classes I saw have a constructor, destructor, methods running the allegro functions, and getters/setters for data fields. Nothing more. I can't imagine it being more sparse, and so I don't understand what annoyed DanielH, what he wanted to avoid.

DanielH
Member #934
January 2001
avatar

Not annoyed, but curious about other people's opinions on the subject.

I like to design my code, for any project, with the intention to share with others. Even if they never do. Personally, I prefer to use std::string for everything because it's easy to use. I doubt that I would ever have a real need to use Unicode, but this library encompasses Allegro and should use ALLEGRO_USTR for strings.

Edgar Reynaldo
Member #8,592
May 2007
avatar

The need for Unicode comes in when you want to make translations of your program for others to use. std::string and std::wstring don't really cut it for unicode support. It's something I have yet to tackle in my GUI. For now I just use std::strings, which I know I will have to replace someday.

bamccaig
Member #7,536
July 2006
avatar

I believe that the preferred string class for C++ is CBString from the bstring/bstrlib library. I believe that Unicode was one of their goals, along with performance and bells.

gillius
Member #119
April 2000

Use whatever is easiest. Your time is far more valuable than computer time and honestly a GUI is not a real-time operation. If you shave off a few microseconds in your string handling no one could even observe it.

Maybe I'm just getting too old. Maybe I've seen the debate too many times. I remember spending too much time worrying about "bloat". I worked with another developer who refused to use virtual functions because they were "too bloated." Now my viewpoint is, I had these same questions back in the 90s when I had a machine with 32Mb of RAM and 100mhz CPU now I have 8192Mb of RAM and 4 3500mhz CPUs. If the worry wasn't relevant then, it certainly isn't relevant now, at least for the software we write. Plus, I write code in Java, so I swim in the bloatiest of bloated code, and I'm still doing fine.

The rules of optimization haven't changed: Don't optimize prematurely. Complexity (O(n) vs O(n^2)) blows away any other form of optimization. A quicksort in JS on your phone will destroy your C++ bubble sort on your core i7.

I do add one caveat. When picking a framework technology, don't be stupid. This is an exception to the optimize prematurely rule. I did poke fun at JS and C earlier, but using something like JS for example puts limits on the maximum performance you can achieve. So, if you think you'll get to the point where you must compete on performance, don't make a choice that forces a rewrite. But, for personal/hobby software, you'll never run into that. For most enterprise or line-of-business/professional software, even then, you won't run into that limit. Games and media (codecs) are where it matters. So you're in the game field, but don't fool yourself into thinking you have a team of 30 devs to work on figuring out a new way to do strings...

Gillius
Gillius's Programming -- http://gillius.org/

bamccaig
Member #7,536
July 2006
avatar

^ With that, which is a good post, note that it's very easy to get distracted from your goal and head off on a useless tangent. That's fine if it's just for fun. If it's professional or you actually aim to make something of your time then you need to learn to recognize the problem and cut it off. It's rarely practical to do things perfectly. You will likely have to compromise to get things done. You need to decide what matters most and focus on solving that. In the professional world, we try to break problems down into pieces, track them, and prioritize them. That way if you see that some improvement could be made you could make note of it, but keep more important things at the top of the priorities and work on those instead. If things get slow you can always fall back on trying to solve the other problem. But practically, if the gains are minimal or negligible then it probably isn't worth your time. Computers are cheap compared to manpower. That's why it's ridiculous when any employer doesn't equip their people, especially computer people, with top of the line rigs.

Gideon Weems
Member #3,925
October 2003

DanielH said:

Not annoyed, but curious about other people's opinions on the subject.

My stance concerning class bloat changed radically. I cannot say exactly when, but it was relatively recent. I used to write, for example, modifier and accessor class members. Now, I shudder at that sort of code. I write in C and make structs. It's easy. I feel unburdened.

princeofspace
Member #11,874
April 2010
avatar

write in C and make structs. It's easy. I feel unburdened.

Man, I couldn't agree more. C is the king!

bamccaig
Member #7,536
July 2006
avatar

C# supports accessors (i.e., properties) natively. It also supports automatic implementations (i.e., default implementations just returning or setting a standard field). 99.999% of properties in C# are just passthroughs to fields. I really hope the compiler optimizes them away because otherwise it's ridiculous waste. Dereference the object reference, then dereference the property method, call it, and finally get the value. Instead of dereference the object reference and get the value. And that's not counting polymorphism overhead...

Certainly there's value in just having access to the data and being able to manipulate it... But of course, you also have to accept that your idiot colleague is going to transform the object into an invalid state by mistake and the code is going to crash 3 files away in an unexpected way... There is benefit to accessor methods. If anything, it would be good for the language to support direct access (or as I said, support a rich property syntax, but optimize the default case).

Edgar Reynaldo
Member #8,592
May 2007
avatar

princeofspace
Member #11,874
April 2010
avatar

How hard is it to write public :?

Not to trifle over semantics here, but in c++ everything inside of a struct is public by default. For instance:

#SelectExpand
1 2class test 3{ 4 5 int foo; 6 7}; 8 9int main(void) 10{ 11 12 test bar; 13 bar.foo++; 14 return 0; 15 16}

The compiler will yell at you because "foo" here is private:

./main.cpp: In function int main():
./main.cpp:15:9: error: int test::foo is private within this context
     bar.foo++;
         ^~~
./main.cpp:7:9: note: declared private here
     int foo;
         ^~~

Change it from a class to a struct and the error goes away.

bamccaig
Member #7,536
July 2006
avatar

I actually rather like the idea of immutable types as well. It won't work well for large objects for performance reasons, but for small structs it's generally just fine. Immutability aids in writing correct code knowing that the object "cannot" change and anything referencing it can rely on that. Need to change the state? Just create a new object!

#SelectExpand
1#include <iostream> 2 3struct Foo 4{ 5 Foo(void): bar_(0), baz_(0) {} 6 Foo(int r, int z): bar_(r), baz_(z) {} 7 8 int bar(void) const { return this->bar_; } 9 int baz(void) const { return this->baz_; } 10 11 Foo frobinate(int r, int z) 12 { 13 return Foo(this->bar_ + r, 14 this->baz_ * z); 15 } 16 17 Foo frobinate(const Foo & f) 18 { 19 return this->frobinate(f.bar(), f.baz()); 20 } 21 22protected: 23 int bar_; 24 int baz_; 25}; 26 27int main(int argc, char* argv []) 28{ 29 Foo f; 30 Foo g(5, 10); 31 Foo h = f.frobinate(g); 32 33 std::cout << "{'bar':'" << h.bar() 34 << "','baz':'" << h.baz() 35 << "'}" << std::endl; 36 37 return 0; 38}

Edgar Reynaldo
Member #8,592
May 2007
avatar

Not to trifle over semantics here, but in c++ everything inside of a struct is public by default. For instance:

Not to trifle over encapsulation, but not everything should be public. In C you have to accomplish that with hidden headers and opaque structs. C++ does it naturally. :/

princeofspace said:

Change it from a class to a struct and the error goes away.

The only error there was not using the public label. structs shouldn't even exist inside C++, they're only there to hold the hands of C programmers who can't grasp encapsulation.

Mark Oates
Member #1,146
March 2001
avatar

I agree that structs are nice. There is some epiphanic purity to them.

bamccaig
Member #7,536
July 2006
avatar

Busted:

#SelectExpand
1struct Date 2{ 3 int year; 4 int month; 5 int day; 6}; 7 8Date foo(Date d) 9{ 10 // Oh shit... 11} 12 13int main(int argc, char * argv[]) 14{ 15 Date d = { 2016, 13, 45 }; 16 Date d2 = foo(d); 17 18 // WTF is d2...? 19 20 return 0; 21}

Mark Oates
Member #1,146
March 2001
avatar

What's up with your code bam? You should be getting a warning when you try to compile:

struct.cpp:11:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^

princeofspace
Member #11,874
April 2010
avatar

structs shouldn't even exist inside C++, they're only there to hold the hands of C programmers who can't grasp encapsulation.

Lots of languages bend over backwards to be compatible with C, and C++ is perhaps the strongest example. Because C has no (built-in) concept of classes, POD structs are necessary.

Back on topic: I used to be concerned about this sort of thing until I read about how struct padding works. In other words, the computer will "bloat" a struct by a few bytes here and there, to more naturally align members according to your architecture. Sprites data (before media assets) in my game is no more than 256 bytes, more than enough for my needs.

As others here have commented, even older hardware can readily handle a largish class.

 1   2 


Go to: