Looking for C++ Code Structure Advice From the Pros

Started by
32 comments, last by M2tM 15 years, 2 months ago
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.
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.
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.
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]
_______________________"You're using a screwdriver to nail some glue to a ming vase. " -ToohrVyk
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.
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.
_______________________"You're using a screwdriver to nail some glue to a ming vase. " -ToohrVyk
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.
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.
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.)
_______________________"You're using a screwdriver to nail some glue to a ming vase. " -ToohrVyk
These were very helpful to me.

Single responsibility Principle.

Open-Closed principle.

Liskov substitution principle.

Dependency Inversion Principle.

This topic is closed to new replies.

Advertisement