C++ style

Started by
7 comments, last by visitor 15 years, 9 months ago
Hi, I am an amateur coder, I have recently finished an engine, its 10 000 lines of code, in one file, I use lots of global variables, theres not one "class", I have used dynamic memory zero times. Detest me. I want to improve my style for future projects, and am hoping for your advice on a couple of questions and in general. How I have survived so far: I use c++-structs with functions, which is as far as I understand it like a class with everything public. Whenever there is a function I want to use in different structures like MoveXY() I make it a global function. MoveXY(&enemy.x,&enemy.y,enemy.speed,...,...etc); 1) Is using classes superior? Or is this something argued about or situationspecific. I understand that classes allow very capsulated programming. But I found that it gave me more coding work and uglier calls. Whats up with that? 2) Global variables. I use global variables(i have maybe 50-100) for everything that needs to be remembered for more than one frame. Is this the correct use? For example I have function RenderButtons() which does in the few times the player is shopping need to know which Button is the active one, so I have a global variable: int menu_buttonactive = 0; 3) Inheritance. Is this something I should cluster my code with to avoid writing it twice? Or something thats not really useful for a game coder unless he has a specific technology in mind. I have two structs, one is enemys[100] and one is particles[1000]. The game is OpenGl 2d and both could use the same basic attributes, like x,y,w,h,velx,vely... (maybe overlapping function, which I currently make global functions, too). Is this worthy of inheritance? 4) Optimization. Should I instead of func(int bla) give a pointer func(int* bla). This is quicker, right? Or is the compiler capable of optimizing the prog itself that far. Also should I write inline infront of everything, or can I rely on the compiler? 5) Dynamic lists. I havent used them, because I was too lazy too code them, and scared to inflict the anger of mighty memory. I dont really *need* them. The question for me is speed. Is int* pointer = new int; pointer = 5; slower than nr = 5; ? My main performance loss (though Im running at 70 fps on a 3Ghz machine) are for-loops. Lets say I have 1000 enemys with different weapons and each has a max of 20 shots at a time available. If I do this static im wasting a lot of loops for inactive shots. I should use an dynamic list here? does managing the list cost significant cpu? Is the memory loss of not using a list significant? Would it be optimal if I create a static array of objects and a dynamic list to keep track of the active objects?
Advertisement
I don't have the time to respond to all of these, but #4 and #5 caught my eye.

#4)

A pointer is 4 bytes wide. An int is (usually) 4 bytes wide. So passing one or the other makes no difference to speed.

If you're so worried, do func(const int& blah). But really, this is pointless also.

#5)

Don't do that. There's no point to allocate 4 bytes (once again, usually) on the heap for one int.

And the overhead of std::vector, std::list, etc is minimal. You shouldn't need to declare and allocate raw arrays on a normal basis.

And in general..

10,000 lines of code in one file is insane. Split it up into multiple files at least. I don't know how you even managed the code like that.
You'll probably get a lot of useful feedback here - most of it will come from more authoratative sources than I, but I'll go ahead and offer a few thoughts:
Quote:I have recently finished an engine, its 10 000 lines of code, in one file, I use lots of global variables, theres not one "class", I have used dynamic memory zero times.
How are your compile times? One of the first things you'll probably want to do is start organizing your code differently (i.e. multiple header and source files).
Quote:I use c++-structs with functions, which is as far as I understand it like a class with everything public. Whenever there is a function I want to use in different structures like MoveXY() I make it a global function.
MoveXY(&enemy.x,&enemy.y,enemy.speed,...,...etc);
Classes and structs are basically the same in C++ (the only difference being default privilages for inheritance and member data). It looks like you're basically doing it the 'C' way; in C++, you would more typically make the member data private and manipulate it using member functions.
Quote:1) Is using classes superior? Or is this something argued about or situationspecific. I understand that classes allow very capsulated programming. But I found that it gave me more coding work and uglier calls. Whats up with that?
The syntax shouldn't be uglier - can you give an example?

As for coding work, it may just be unfamiliarity with the idioms; once you get comfortable with C++ and object-oriented design, it shouldn't be that much more work.
Quote:2) Global variables. I use global variables(i have maybe 50-100) for everything that needs to be remembered for more than one frame. Is this the correct use? For example I have function RenderButtons() which does in the few times the player is shopping need to know which Button is the active one, so I have a global variable:
int menu_buttonactive = 0;
It is often wise to avoid global data (for reasons covered in many previous threads on this forum). With proper design, it's quite possible to create a non-trivial application with little or no global data.
Quote:3) Inheritance. Is this something I should cluster my code with to avoid writing it twice? Or something thats not really useful for a game coder unless he has a specific technology in mind.
Inheritance is one way to avoid duplicating code, but it's not the only way (nor always the best). The optimal solution will of course depend on the circumstances.
Quote:I have two structs, one is enemys[100] and one is particles[1000]. The game is OpenGl 2d and both could use the same basic attributes, like x,y,w,h,velx,vely... (maybe overlapping function, which I currently make global functions, too). Is this worthy of inheritance?
Yes, you could use inheritance for that. Another option would be composition using 'components', but this approach can be a little bit more involved to implement.
Quote:4) Optimization. Should I instead of func(int bla) give a pointer func(int* bla). This is quicker, right? Or is the compiler capable of optimizing the prog itself that far.
For small built-in types, it shouldn't matter (in fact the pointer version may be slower - not sure about that though). For larger objects, passing by pointer or reference can have performance benefits (an important aspect of becoming a proficient C++ programmer is knowing when to use references, and when to use pointers).
Quote:Also should I write inline infront of everything, or can I rely on the compiler?
AFAIK it's usually safe (and perhaps best) to just leave it up to the compiler.
Quote:5) Dynamic lists. I havent used them, because I was too lazy too code them, and scared to inflict the anger of mighty memory. I dont really *need* them. The question for me is speed.
It's possible (maybe even probable) that performance won't be as much of an issue as you think it is. As such, you should use whatever method/tool/approach best solves the problem at hand. Often times this will be dynamic allocation (e.g. for an array whose size is determined at run time). You'll want to learn about dynamic memory management in C++, and then about the Standard C++ Library (which includes container class templates - e.g. std::vector - that make manual memory management much easier).
Quote:Is

int* pointer = new int;
pointer = 5;

slower than

nr = 5;
It would actually be *pointer = 5;. As for your question, the former may be slower in some cases, but I'm not positive about that. Also, I don't care :-) Why? Because most useful optimization is going to happen at a much more abstract level (e.g. choice of algorithm) than what you have presented here.

In other words, don't worry about which of the above is faster. Just use the one that makes more sense.
Quote:Original post by schattenmaennlein

1) Is using classes superior? Or is this something argued about or situationspecific. I understand that classes allow very capsulated programming. But I found that it gave me more coding work and uglier calls. Whats up with that?
Probably your inexperience at object oriented design. I doubt you will find many who claim structured programming is better than OO but it is more of a what are you good at sort of thing.
Quote:

2) Global variables. I use global variables(i have maybe 50-100) for everything that needs to be remembered for more than one frame. Is this the correct use? For example I have function RenderButtons() which does in the few times the player is shopping need to know which Button is the active one, so I have a global variable:
int menu_buttonactive = 0;
Sounds like you just don't know that much about software design, try working on that.
Quote:

3) Inheritance. Is this something I should cluster my code with to avoid writing it twice? Or something thats not really useful for a game coder unless he has a specific technology in mind.
Inheritance should only be used when you need to be polymorphic on a type. Every other situation should be handled through composition. in your example no it isn't needed.
Quote:

4) Optimization. Should I instead of func(int bla) give a pointer func(int* bla). This is quicker, right? Or is the compiler capable of optimizing the prog itself that far.
For an int the pointer version is probably the slower version. you should only pass by pointer if you need pointer semantics. pass by reference is the preferred speed version, and then it is only necessary for classes that are expensive to copy(integers aren't).
Quote:

5) Dynamic lists. I havent used them, because I was too lazy too code them, and scared to inflict the anger of mighty memory. I dont really *need* them. The question for me is speed.
Don't use it if you don't need it. but remember RAII is your friend.
http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Resource_Acquisition_Is_Initialization
Quote:
Is

int* pointer = new int;
pointer = 5;

slower than

nr = 5;

?

yes (*pointer) = 5 requires the pointer to be dereferenced so you get two memory accesses instead of one.
Quote:
My main performance loss (though Im running at 70 fps on a 3Ghz machine) are for-loops.


are you sure? have you profiled your code?
you could sort the lists so that all active shots occur at the beginning of the list then quit processing as soon as you find an inactive shot.
10,000 lines of code in a single file! I cannot imagine this. Splitting your code into many files is not only relatively easy, but vital to keep your code manageable. It probably takes ages to compile your code.

1) classes vs structs

There are two differences between classes and structs in C++, default member access and default inheritance model. Classes use private for both, structs use public.

More important than classes vs structs are concepts such as RAII and encapsulated data types (where it makes sense).

2) I would consider 50 - 100 global variables excessive. I usually get away with one or two - and I make those global because I am lazy.

3) Inheritance can make sense in some situations. Yes, enemies and particles have certain attributes in common, so a superclass could cut down on code duplication.

4) Use pointers where pointers make sense. Basically, if you need nullable, reseatable reference semantics. If you need reference semantics without the other two, use a C++ reference. Otherwise use a value or possibly a const reference. I generally pass by value for primitives, and const references for complex types.

Forget "inline", the compiler is free to ignore it.

5) Dynamic data structures are important. I would be using some kind of dynamic list such as std::vector<> instead of arbitrarily sized arrays for your enemies and particles.

// [is]int* pointer = new int;pointer = 5;// slower thannr = 5; 

Yes. Don't use dynamic allocation when it doesn't make sense.

Quote:does managing the list cost significant cpu?

Not really.

Quote:
Is the memory loss of not using a list significant?

Probably not for a 2D game. But there is no need to loop through thousands of enemies that aren't active.
Hm, I won't (try to) answer all of your questions, but a few things came to mind while reading your post.

For one thing, your code will certainly benefit from using C++ idioms. From what you've described, your code probably bears more similarity to a pure C program than a C++ program.

Many people have said this on this board before, but... don't worry about optimizations too early! If you're concerned about the overhead of using STL containers, it doesn't hurt to do research or ask, but don't sidestep them from the get-go and use clumsier solutions because you just have a feeling that they'll be a bottleneck. Typically, it's better to write maintainable, coherent, and flexible code the first time and optimize it later.

Objects and inheritance are a big part of C++. It's true that these features can be overused or misused, but that shouldn't scare you away from them. Polymorphism is certainly worth it! You can get this effect using C, but it's difficult to write correctly and use. Why not use the features built right into the C++ language?

Global data is not a good thing. If the index of the active button in your GUI is nothing more than a globally accessible int, then any part of your code (or client code!) can inadvertently alter it. What if there are preconditions and postconditions? What if the active button depends on some condition or other value? How will you control this? You could screen the value through a function each time your program needs to use it, but now you need to perform checks all the time. What happens when you forget to screen the variable? What if it's previous state is important to its consistency (in which case, screening it before use won't work either)? This can be solved via OO using encapsulation.

Be careful about pointers. In terms of your int example, it should be written:

int a = 5; // Good and dandy.int* b = new int();*b = 5; // Notice the dereference (*)!

Don't forget to dereference! :-)

In regards to parameters, pointers and references are good because they typically require passing only 4 bytes or so of data (4 bytes are copied). When a class requires 1KB of memory, it's convenient (i.e., faster) to pass a pointer to its memory location rather than making a complete copy. Also, it's often incorrect to make a copy (i.e., sometimes copying makes no sense). This is all part of pass-by-value and pass-by-reference semantics.

You'll also find that classes can help organize and modularize your code. The OOP paradigm is popular for reuse. You'll probably want to split that massive source file into smaller pieces, and classes can help do that for you (and make many of those smaller pieces easier to reuse).

Another point about using pointers and dynamic memory allocation is... well, memory! Variables declared auto (which all variables are by default) are stored on the stack. The stack is comparatively limited. After delving into numerous functions and allocating many non-trivial data structures, your program's stack will quickly fill up. The heap, where new'ed data resides, is much, much bigger.

some_big_object big_object_stack_array[9999999L]; // Hm, probably won't play nice!std::vector<some_big_object> big_object_vector(9999999); // Much more likely to work without running out of memory.

std::vector dynamically allocates memory, so it's more likely that the vector in the above code would operate just fine while the stack-based array will squander the memory in your program's stack.

Just my two cents. :-) Hope this helps!
Quote:Original post by schattenmaennlein
1) Is using classes superior?

No, writing maintainable, reusable, semantically correct code is superior. Classes are a tool for doing so, but like any other tool must be used appropriately. Some fall into the trap of pushing everything into a class (and some languages encourage it, too); this is foolish.

What classes provide are representations of state and the valid operations on that state in a single location. Introspection, reflection and code intelligence tools prove more useful when they can determine more about the intent of code based on expression, and classes are one - but far from the only - way of expression a measure of intent.

Summary: use them when appropriate.

Quote:2) Global variables.

Global variables lack ownership, and as such are virtually impossible to guarantee. In a sufficiently complex program, you can not make any assumptions about the value, or even range of possible values, of a global variable, as it may have been modified from any other code site at any time.

Global variables are sometimes masked in fancy-sounding constructs like singletons. The same flaws exist, however, and the same rules apply. (Monostates are a bit of an exception because they are immutable, but they often might as well be magic values.)

Summary: avoid using them.

Quote:3) Inheritance.

Prefer composition to inheritance. Inheritance gets overused in C++ because it is the primary means of effecting polymorphic dispatch (templating is the other). Don't avoid inheritance when it logically expresses intention, but whenever you find yourself create an Object class so that all functions can generically access more concrete types, it may be time to look at everything from generic programming to runtime typing, to broader but shallower inheritance of parameter types, to predicate-based algorithms.

Summary: use in moderation.

Quote:4) Optimization.

Write your code. Make sure it is correct. Then profile, and optimize areas the profiler indicates are bottlenecks. Do not prematurely optimize, and adequately inform yourself so you're not writing a bunch of code to no avail. Modern compilers are pretty smart, too, so make sure you know for a fact that the generated code is slow, and that you can do better. (Your example "optimization" is pretty useless, for instance.)

Do not lightly sacrifice readability and maintainability in the name of performance.

Summary: carefully.

Quote:5) Dynamic lists.

They're faster than you think. Invariably. And in some cases, using them can save you memory and improve performance. Before you ever say "X is slow," profile.

Summary: yes.
Ok cool learned a few things.

As for your questions:

In which way got your incompetent class-coding ugly?

Probably through bad composion. I actually couldnt find an example.
I just remember having lots of
blabla->Getbla(blabla2->Getbla1(blabla->SearchedBla),blabla2->Getbla2(blabla->SearchedBla)));
Most likely things which should have been done in the classes itself or were overcapsulated.

Hows your compile time?

Surprisingly ok. The compiler still only needs a few seconds.

How did you code like that?

Bookmarks. And global functions for the projects subparts. Which is like different files I guess, only less efficient.

Are you sure the for-loops are your main-efficiency loss?

Well no they arent, but since I have optimized collision-detection and ki its where I have most room to improve. It gets ugly quick because of things like: collisiontest 1000Enemys * 100Shots, and later, draw 1000 * 100 shots, theres a lot of exponentional growing of cpu-usage if I want to get more things possible.



1) Is using classes superior?
No its just a slightly different tool, the use is important.

2)How many global variables should you have?
About 0. I suppose through capsulation.

3)Inheritance.
Use in moderation, prefer composition and read up on other techniques.
It seem inheritance should almost always only be used when you dont know the exact type of the object before runtime.

4)Optimizations
Use a profiler, then optimize to needs. Giving pointers to functions is not superior with 4bit datatypes and should only be used for structs and such. References are generally quicker and safer, but a little less powerful than pointers.

5)Dynamic lists
Dynamic lists are cool. Use them when appropriate.


Guess I have to read up a few things before my next project, ty for your input.
Quote:
I just remember having lots of
blabla->Getbla(blabla2->Getbla1(blabla->SearchedBla),blabla2->Getbla2(blabla->SearchedBla)));
Most likely things which should have been done in the classes itself or were overcapsulated.


Your assessment is probably correct.

When you find you are using lots of getters and setters it might mean that the class expects the user to code the logic that should be rather implemented as class logic. Compare the two examples that do exactly the same thing.

#include <iostream>#include <string>std::string pluralize(const std::string& s, int n){    return n == 1 ? s : s + "s";}class Living1{    public:        void set_max_age(int n) { max_age = n; }        int get_max_age() const { return max_age; }        void set_age(int n) { age = n; }        int get_age() const { return age; }        void set_name(const std::string& s) { name = s; }        std::string get_name() const { return name; }    private:        std::string name;        int age;        int max_age;        bool alive;};int main(){    Living1 a;    a.set_max_age(10);    a.set_age(0);    a.set_name("Joe");    while (a.get_age() < a.get_max_age()) {        a.set_age(a.get_age() + 1);        int age_left = a.get_max_age() - a.get_age();        std::cout << a.get_name() << " is " << a.get_age() << pluralize(" year", a.get_age()) << " old and has "        << age_left << " more" << pluralize(" year", age_left) << " to live.\n";    }    std::cout << a.get_name() << " has died at an age of " << a.get_age() << '\n';} 

#include <iostream>#include <string>std::string pluralize(const std::string& s, int n){    return n == 1 ? s : s + "s";}class Living2{    public:        Living2(const std::string& s, int max): name(s), age(0), max_age(max) {}        void do_age() { ++age; }        bool is_alive() const { return age <= max_age; }        void report() const;    private:        std::string name;        int age;        int max_age;};void Living2::report() const{    if (age <= max_age) {        int age_left = max_age - age;        std::cout << name << " is " << age << pluralize(" year", age) << " old and has "        << age_left << " more" << pluralize(" year", age_left) << " to live.\n";    }    else {        std::cout << name << " has died at an age of " << max_age << '\n';    }}int main(){    Living2 b("Joe", 10);    while (b.is_alive()) {        b.do_age();        b.report();    }} 


Now the second version doesn't provide any accessors at all. It's usage is therefore much more limited (it is limited only to intended purpose, disallowing all misuses, such as letting a Living age more than one year at a time, getting younger etc). (Some accessors might be added, though: a person may change its name - but should disallow empty string as name, you may want to query the age - but shouldn't be able to set it arbitrarily etc.)

This topic is closed to new replies.

Advertisement