• Advertisement
Sign in to follow this  

Looking for C++ Code Structure Advice From the Pros

This topic is 3310 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

In my experience writing code in C++ seems to be, more than any other language, a puzzling and difficult challenge. It's not only that you have to figure out how to implement functionality but also how to make it fit into the intricately design software architecture. In C, for example, everything is basically flat and globally accessible. In C++ it seems easy to wind up in a situation where what you need is locked away where you can't use it. To complicate matters more I have read many comments on various programming practices which should be avoided (perhaps rightfully so), but it is not always entirely clear what better alternatives should be used. I'm having a hard time picturing what good OOP looks like. Individual classes and objects are simple enough, but what about the whole grand overall structure of the software? If both singletons and globals should be avoided, what would be the best way of having access to functionality from anywhere it might be needed? Is everything that is needed suppose to be passed down the chain of function calls? I don't know. I would like to hear a little from experienced C++ programmers their philosophies on designing software. What does the overall structure of a well designed game look like? How do the systems interact with each other? I know a reasonable amount about C++ the language, but I find I often paint myself into corners when it comes to the overall structure of software projects. Can anybody offer any advice on how to write well designed C++ source code? What principles should be kept in mind while programming? What guidelines should be followed? Thanks.

Share this post


Link to post
Share on other sites
Advertisement
Quote:
In my experience writing code in C++ seems to be, more than any other language, a puzzling and difficult challenge. It's not only that you have to figure out how to implement functionality but also how to make it fit into the intricately design software architecture. In C, for example, everything is basically flat and globally accessible. In C++ it seems easy to wind up in a situation where what you need is locked away where you can't use it.
Several problems with what you have said. You imply that it is a C++ specific issue when it comes to software design. It is not. There are certain challenges when it comes to C++, but stating that you have trouble with designing the software and programming principles because of C++..?

Furthermore, you state how everything in C is flat and globally accessible. What could you possibly mean by that. Because in fact, everything is not globally accessible. If I have a local variable a in function foo, it is not globally accessible.

Quote:
To complicate matters more I have read many comments on various programming practices which should be avoided (perhaps rightfully so), but it is not always entirely clear what better alternatives should be used.
Pointing out bad solutions is far easier than seeing good alternatives. Even the word alternatives might not be appropriate depending on the problem.

Quote:
I'm having a hard time picturing what good OOP looks like. Individual classes and objects are simple enough, but what about the whole grand overall structure of the software? If both singletons and globals should be avoided, what would be the best way of having access to functionality from anywhere it might be needed? Is everything that is needed suppose to be passed down the chain of function calls? I don't know.
Why do you need access from everywhere? Do you think it's good design to have variables that everyone can mutate?

There is no principle that says if you do X, you code will be good. There's philosophies that you can follow to avoid pitfalls, but it doesn't mean what you make will be good. Good design comes from experience. You understand the implications of certain design choices.

Share this post


Link to post
Share on other sites
Sorry, I wasn't entirely clear while expressing myself.

When I said that C++ seemed more puzzling and difficult, I meant that of the half-dozen-or-so languages I've tried out over the years C++ is the only one that had a tendency to give me headaches -- I'm not entirely certain exactly why that might be.

When I said that C was basically "flat and globally accessable" I was thinking most specifically about functions and how they do not exist inside of objects. The word "everything" was a little too inclusive.

When I mentioned having access to functionality from anywhere I did not imply fiddling with the variables belonging to other objects, but having the ability to make use of those objects. For example, in one game I was working on I've seen an ever growing number of objects being passed down through functions -- this is clearly not the best way to have access to them.

I was actually hoping for a more positive and helpful response than the one you had given. A lot could be learned from people who already have years of experience.

Share this post


Link to post
Share on other sites
While I have been programming for several years there are many people on this forum who are better programmers than I am. With that qualification stated I'll give a few tips. Without a more specific question that's all that I can really do.

First and foremost if you are having difficulty with OOP it is not a C++ specific problem. Any object oriented capable language will have similar issues, but you may be unaware of them in looser typed languages. One thing that C++ does that many (but not all) languages do not do is offer multiple inheritance which should be really only be used sparingly to inherit multiple interfaces rather than multiple implementations. This is a sticky subject in that some people suggest it is a mistake to use multiple inheritance at all. This is all getting ahead of our point, but it is important to know that in general your problems are probably not language based.

Now, that said I can highly recommend you invest in Design Patterns as it is a very good reference for several re-usable solutions for common object oriented problems. It happens to have examples in C++ and Smalltalk.

A few important things to understand is that there are two basic ways of letting classes interact. One is through inheritance, this is a compile time dependency which is generally more inflexible when used to modify behavior. The other method is through composition in which one object contains a reference to another and uses that object's well defined interface to achieve its own goals.

Where possible you should favor composition over inheritance as it allows for run-time behavioral changes and also enforces a situation where the user of that object does not need to also understand its implementation.

A few general tips about classes and functions. Functions should strive to take no more than 2 arguments and they should be very small in an object oriented language. If you find a function is doing more than one thing break it up and make an object of it, or if it is in an object, break it up and either make private or public members out of those separated. This will greatly increase readability of your code if you also follow good naming conventions.

Classes themselves should also not be too large. This is an excellent guideline to follow: Single Responsibility Principle

"A class should have only one reason to change"

Which brings me to another book recommendation: Clean Code by Robert C. Martin

It isn't feasible to provide a good answer to your question on the forums, what I've done is hopefully pointed you in a direction for more answers. Again:

Design Patterns
and
Clean Code

You'll be hearing "don't do this or this or this" a lot because C++ is concerned with things in the following order:
1) Efficiency - This affects nearly every aspect of the language, "you only pay for what you use" is a very specific requirement of any language feature provided.
2) Backwards Compatibility - No new language feature will ever significantly alter legacy code except unavoidable naming clashes which they strive to avoid for common libraries anyway. This also means that you get artifacts which don't exactly fit with "good coding practice" for C++ because they are carryovers from C which it was originally based on.
3) Flexibility - You will more likely see a behavior with an undefined result than you will an imposed limitation. You won't ever feel steered towards one and only one solution for any large problem.

So what you have is a language that will not enforce any good programming techniques except for strong type safety which can actually be circumvented when you need to. Keep in mind C++ is further complicated by the fact that it is not a strictly object oriented language, it does support object oriented programming but it can also support procedural methods. It is a multi-paradigm language which can cause confusion because not only does it supply flexibility of low level code, but also on an architecture level. As a result it is much easier to build improper and terrible code than it is good code.

As a result, and because many people learn C methods and then C++ you'll see a lot of focus on what not to do instead of what -to- do. For every rule there is an exception though, goto can be used reasonably to break out of deep nested loops in performance code for example even though it can be terribly employed and no programmer will suggest that good code contains that statement.

One final book suggestion, The C++ Programming Language by Bjarne Stroustrup. It is not focused on beginners, but spends a lot of time focusing on what "proper c++" is and how to go about programming with the language.

[Edited by - M2tM on January 23, 2009 12:11:53 AM]

Share this post


Link to post
Share on other sites
I think this is a very good question and unfortunately I doubt many people will give you their 2 cents since it's not very specific.

I'll give you my opinion, though I'm far from a pro. I've had reasonable success with a global application object that contains all sorts of manager objects. Compile time stinks with this kind of setup, since if you change the app everything has to recompile. On the other hand, you don't have to write lots of message passing type code since you can just include the app object and get what you need. I suspect this style is problematic with multi-threaded code and leads to more errors once you have more than one programmer editing things. The alternatives are storing things in local objects or passing references around in message type functions.

My action-type games tend to have a rough architecture of ObjectManager, RenderManager, PhysicsManager and Application. The application manages the managers, loads levels, initializes stuff, etc.

A bit more abstractly, I try to use object composition whenever possible and avoid big inheritance hierarchies. For example, if I'm writing a space shoot-em-up, instead of making class ShipWithAWeapon : Ship, I'd make class Weapon, throw the logic for shooting in it and then any ship that needed a weapon(s) would simply have a weapon object.

There's really lots to say here, but I'll stop since I think I addressed your original question.

Share this post


Link to post
Share on other sites
I just found this section in the C++ programming language third edition which perfectly illustrates a number of points I made in my above post. Direct from the original creator of C++:

"Macros are very important in C but have far fewer uses in C++. The first rule about macros is: Don't use them unless you have to. Almost every macro demonstrates a flaw in the programming language, in the program, or in the programmer."

Strong words. These types of warnings pepper this book and with good reason.

Share this post


Link to post
Share on other sites
Thank you M2tM, you have been very helpful. As a hobby programmer with next-to-no cash I have to admit I do not own as many good programming books as maybe I should. I will definitely try to get my hands on a few good books about how to write good code.

That Single Responsibility Principle sounds like a good idea. I admit, looking back, I may have a tendency to try to push things into existing classes instead of creating new ones.

Sorry for not really having more specific questions, the the issue is one of abstract complexity. I didn't really know what to ask, as the question has to do with many things.

Thanks for steering me in the right direction. I'm going to have to look into those books.

Share this post


Link to post
Share on other sites
Quote:
Original post by ArmchairArmada
What does the overall structure of a well designed game look like?

You'll know once you finished a few badly designed games.

Share this post


Link to post
Share on other sites
Quote:
Original post by Konfusius
Quote:
Original post by ArmchairArmada
What does the overall structure of a well designed game look like?

You'll know once you finished a few badly designed games.


I disagree. You'll know what badly designed games look like if you finish a few badly designed games but you may not even realize they are bad. Often people are blinded to their programming faults because on a surface level the code may work and they may move on without thinking further on the matter. You will not magically become aware of better methods except by deep introspection (I say introspection because the nature of the problem originated from your own mind) into those mistakes or by learning from other more experienced programmers (and books by those programmers.)

Share this post


Link to post
Share on other sites
Quote:
Original post by M2tM
Quote:
Original post by Konfusius
Quote:
Original post by ArmchairArmada
What does the overall structure of a well designed game look like?

You'll know once you finished a few badly designed games.


I disagree. You'll know what badly designed games look like if you finish a few badly designed games but you may not even realize they are bad. Often people are blinded to their programming faults because on a surface level the code may work and they may move on without thinking further on the matter. You will not magically become aware of better methods except by deep introspection (I say introspection because the nature of the problem originated from your own mind) into those mistakes or by learning from other more experienced programmers (and books by those programmers.)

That's my opionion, too.

Quote:
but you may not even realize they are bad

ArmchairArmada expressed interest in improving his design skills. I think he will keep a critical eye on his work as he makes progress.

Share this post


Link to post
Share on other sites
It sounds to me like your difficulty is with OOP, and not specifically with C++.
What other programming languages have you used, and which of those are you fluent at?

Share this post


Link to post
Share on other sites
Quote:
Original post by theOcelot
These were very helpful to me.

Single responsibility Principle.

Open-Closed principle.

Liskov substitution principle.

Dependency Inversion Principle.


Thanks, I meant to link all of those, but could only remember the exact name of the SRP off the top of my head while I was writing that response. ++ for being awesome.

Share this post


Link to post
Share on other sites
I suppose I may have more difficulties with object oriented design than with C++ specifically. I understand classes and objects and how to use them, but I suppose I wasn't entirely certain about how to use them well. I suppose I directed my frustration towards C++ mainly because of the languages I've used, C++ is the only one I've really attempted large scale projects with -- where greater complexities would naturally arise. I may have been a little hasty with targeting C++ specifically.

To answer iMalc's question about the languages I've used I could give a brief history. I began with Commodore 64's basic, moved to QBasic after getting an 8088, Visual Basic, about seven or eight years ago I began using C++, javascript, PHP, and lately I've been using mostly Python. However, programming, being more of a hobby, there have been many times when months have gone by without really doing much programming at all -- I've been hoping to become a little more serious about it, though.

I suppose when using interpreted languages on small personal experiments, just playing around, there is less pressure for doing things well -- it's very different from a large project where quick hacks early on could come back to bite me. The temptation to get things up and running as quickly as possible can be hard to resist.

Thanks for those articles. For a second there I forgot I downloaded them before leaving for work. I'll have to take a look at them.

Share this post


Link to post
Share on other sites
Quote:
Original post by M2tM
I disagree. You'll know what badly designed games look like if you finish a few badly designed games but you may not even realize they are bad.


The best design is the one that works for you. You can (and people do) obsess about OO to the point where they never write a line of code, because it may not fit perfectly into the dogma of OOP. Programming paradigms exist to make your life easier, not harder. They are suggestions as to how something may be most easily implemented, not hard and fast rules about how things must be done.

To me, the best way to learn how to structure code is to just write code. Write a small game, then write a bigger one. In the process, you'll want to reuse your existing code, but may find that it needs to be refactored to fit into a larger, more diverse code base. Continue this practice for several projects, and you'll soon realize what works and what doesn't work.

You may not have the prettiest code on the planet. It may have a global or two in it, or have some dirty casts that rely on unsafe assumptions, but it will work. And at the end of the day, the only thing that matters about your code is that it works.

Share this post


Link to post
Share on other sites
Quote:
Original post by Driv3MeFar
Quote:
Original post by M2tM
I disagree. You'll know what badly designed games look like if you finish a few badly designed games but you may not even realize they are bad.

You may not have the prettiest code on the planet. It may have a global or two in it, or have some dirty casts that rely on unsafe assumptions, but it will work. And at the end of the day, the only thing that matters about your code is that it works.


Your first points are all good, so I will simply address this.

The OP is looking for advice on how to become better which, I think we can agree, involves minimizing stuff like this. DOING IS ABSOLUTELY AND ENTIRELY VITAL TO BECOMING A GOOD PROGRAMMER (this is bold and caps so it doesn't get lost.)

That said, I do not believe that you can really transcend the basics without either a -lot- (years and years) of messy code which slowly gets better or alternatively by both applying and learning from others who have already trod that path and figured more graceful solutions to typical problems.

Sure you may have code that works and ultimately getting it done is more important than quibbling about how it could be better. But don't discount advancement in sophistication while also getting things done, because that is an option. It isn't one or the other, doing at the cost of learning or learning at the cost of doing. True masters learn and do in equal measure.

Let me stress that it is important to be unafraid of getting your hands dirty and delving into some code. Ultimately practice is the most important aspect of learning, but temper that with advice and examples from others with more practice than you have (all the while applying what you learn as you learn it) and you will become better quickly.

Share this post


Link to post
Share on other sites
I just finished reading through those articles that theOcelot posted. They were great. I'm going to have to reread them to let them fully sink in. The last one I read, "The Dependency Inversion Principle," totally blew my mind! This solves so many problems! I can see how purely abstract interfaces could help with making extremely flexible and reusable code.

One great thing is that it would greatly simplify dependencies. It always annoyed me when making small changes caused half my application to need to be recompiled!

Wow. Thanks!

Share this post


Link to post
Share on other sites
Yes, I rather enjoyed the Dependency Inversion one too. Oh heck, I'll be honest, it blew my mind too.

My only small problem with those articles is that they teach a rather hard-core OOP. Maybe it works, or has to be that way, in Java, but you don't want to go too crazy with all the interfaces and inheritance and stuff in C++. You want to be a little more pragmatic. C++ is a multi-paradigm language. Make the most of it.

Share this post


Link to post
Share on other sites
Perhaps you would be more comfortable initially just using objects rather than making your own classes. You could program in a procedural way using objects such as those in MFC and the SC++L, then as you get used to how those objects work, and study their source code occasionally, you'll feel comfortable taking the step into writing your own OO code.

Share this post


Link to post
Share on other sites
I was thinking about game entities and their dependencies. Jdindia suggested having a global application object where the necessary systems could be accessed. I didn't really like this idea, since it sort of ties the dependencies in knots.

Instead I thought it would be better if each game entity was passed a pointer to a systems class which contains purely abstract interfaces to the necessary systems. For example: in entity behavior code I might use something like this->systems->audio.play("explosion"), therefore the implementation of audio doesn't matter to entities, just the name of the sound effect.

Likewise there might be this->systems->entity_manager.add("SomeEntity"), where an entity factory might be used to create new entities in the entity manager.

Things seem so much clearer now. Though, maybe, it would be better optimized to use enums instead of strings -- though it might be more difficult to later allow scripting to add functionality?

[Edited by - ArmchairArmada on January 24, 2009 12:19:16 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by ArmchairArmada
Though, maybe, it would be better optimized to use enums instead of strings -- though it might be more difficult to later allow scripting to add functionality?


What about FourCC's?

Share this post


Link to post
Share on other sites
With 64-bit processors fast eight character code comparisons could be made (I assume). However, I have decided that I should favor flexibility and easy programming over optimizations since, as I am working alone, I would rather not needlessly complicate matters. Hobby / indie games do not need every ounce of speed squeezed out of them, I guess. String might suffice.

Share this post


Link to post
Share on other sites
I'm not sure if I should worry too much about obsessively abstracting the libraries used with my programs. I began writing an abstract audio interface, but didn't really know what I needed. It might be more worth it to simply use irrKlang without further abstraction.

Share this post


Link to post
Share on other sites
On second thought. I think I would want to have an abstracted audio interface, since 3D sound would be played relative to the camera and I'd rather not have the entities themselves performing the transformations. Either way I think I'm getting too far ahead of myself. I shouldn't try to program something until I will actually need it. I need to keep this in mind.

Share this post


Link to post
Share on other sites
Quote:
Original post by ArmchairArmada
On second thought. I think I would want to have an abstracted audio interface, since 3D sound would be played relative to the camera and I'd rather not have the entities themselves performing the transformations. Either way I think I'm getting too far ahead of myself. I shouldn't try to program something until I will actually need it. I need to keep this in mind.


It's easy to get caught up in trying to develop the "perfect" solution and not actually getting much done. In general you've got the right idea, build what you need when you need it. As you build new tools make sure they are extensible and try not to paint yourself into a corner, but remember that writers often start with a "rough draft" and refine it in subsequent passes.

You shouldn't be focused on making it perfect the first try, you should be focused on making it work without breaking other code. But you should also try not to let too many brittle and bad coding practices pile up before you re-form them into something better because chances are if you leave something and say "I'll get back to it" you won't. Often I'll write a new function and let it grow as it needs to sometimes up to 500 lines (this is typically the largest I let something get before refactoring) and then after I have it working I'll break it up into more logical pieces.

Later when you have enough of a system in place to worry about control flow beyond each individual portion you'll want to do a broad-phase refactoring before going back to smaller details again.

Following this pattern for new module development:
1. Decide what pieces make up your newest module.

2. Pick a place and start.
3. Write it out and make it work.
4. Refactor your newest code. Go back to step 2 when finished unless you are done the whole module.

5. Concentrate on the interface and make sure it fits within the program. Go back to step 1.


Then of course you can always revise more after you have several modules done, this would be yet another layer on top of my example. The point is it is an iterative process. Don't get too hung up on a single pass because that isn't feasible in real development scenarios.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement