Sign in to follow this  
McCoder

Are classes evil?

Recommended Posts

Looking at the flow of win32 api and opengl makes me wanna stick everything into classes. Is this a good practice? I would want to get so clean that I should read InitSound(); DrawStuff(); DiePainfully(); etc. Good clean code.

Share this post


Link to post
Share on other sites
Quote:
Original post by McCoder
I would want to get so clean that I should read InitSound(); DrawStuff(); DiePainfully();

I fail to see the connection with classes...

Share this post


Link to post
Share on other sites
Quote:
Original post by DevFred
I fail to see the connection with classes...


my take at a translated OP: "these very popular APIs do not use classes. did they avoid them for a reason or is it ok if I encapsulate them?"

IIRC they were originally offered before OO programming was so prevalent. There's not necessarily a good reason for them to not be encapsulated in a class if you find that easier to use.

-me

Share this post


Link to post
Share on other sites
Quote:
Original post by Palidine
Quote:
Original post by DevFred
I fail to see the connection with classes...


my take at a translated OP: "these very popular APIs do not use classes. did they avoid them for a reason or is it ok if I encapsulate them?"

IIRC they were originally offered before OO programming was so prevalent. There's not necessarily a good reason for them to not be encapsulated in a class if you find that easier to use.

-me


thats what I meant, thanks :) sorry, but im not english :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Palidine
IIRC they were originally offered before OO programming was so prevalent. There's not necessarily a good reason for them to not be encapsulated in a class if you find that easier to use.

The Win32 API was written before C++ was prevalent. You can write object oriented code in C, you just need to do it without language support for classes. Ie, you would have structures and a set of functions that operate on said structures instead of just bundling it all into a class with members and member functions. If you want polymorphism you need to add extra code to handle the switching between types manually. For a list of principles that are really important to OO, read this.

Anyway, a good reason to keep the API in C is backwards compatibility and the fact that C++ doesn't have a standard way to decorate names. See this. This makes it hard to interoperate between languages. So, C is often to make it easier to bind things together.

Share this post


Link to post
Share on other sites
Quote:
Original post by nobodynews
Anyway, a good reason to keep the API in C is backwards compatibility and the fact that C++ doesn't have a standard way to decorate names. See this. This makes it hard to interoperate between languages. So, C is often to make it easier to bind things together.


Just out of curiosity, is there a reason there isn't a standard way to decorate names in C++? Seems like the world could benefit from consistent name mangling.

Share this post


Link to post
Share on other sites
Quote:
Original post by MikeTacular
Quote:
Original post by nobodynews
Anyway, a good reason to keep the API in C is backwards compatibility and the fact that C++ doesn't have a standard way to decorate names. See this. This makes it hard to interoperate between languages. So, C is often to make it easier to bind things together.


Just out of curiosity, is there a reason there isn't a standard way to decorate names in C++? Seems like the world could benefit from consistent name mangling.


If you keep reading down that page there is even the heading: "Standardised name mangling in C++".

Share this post


Link to post
Share on other sites
I would say the idea that you have is good, but please don't get carried away.
Just because you think it might need to be stuck into a class doesn't necessarily mean that it needs to be stuck into a class.

My rule of thumb when dealing with OOP is make objects, not classes, and make sure the objects are independent, and have their own responsibilities otherwise I see no use of creating them.

Share this post


Link to post
Share on other sites
Classes are actually a very good for debugging and stopping Linking Errors if you wanted to pass variables around in functions very easily with out having to make them global which sometimes can get the compiler confused if you had tones of header files.

Whats So Great About Classes

I suggest you read it, and find out yourself before you find out the hardway when you first develop your large project.

Share this post


Link to post
Share on other sites
Quote:
Original post by McCoder
So sticking the WinApi Winmain in a class would be classified as "good"?


You can't do that, it won't work.
WinMainCRTStartup wants WinMain not SomeClass::WinMain.
It would be pointless anyway, since WinMain is just an entry point into the application. So in my opinion (I'm sure someone will disagree), it's not classified as a good thing.

Share this post


Link to post
Share on other sites
Quote:
Original post by McCoder
So sticking the WinApi Winmain in a class would be classified as "good"?


A lot of people are going to come in here and tell you how not to put this code in a class. They're going to tell you to follow the single responsibility principle, they're going to stress encapsulation, data hiding, and proper OO paradigms. Most people get so caught up in designing their program in the proper OO way that they never actually finish something. That's why many people are on their second, third or even fourth rewrite of their game-- er, engine.

For a small project like an arcade-style game, things like proper OO design and principles are just going to keep you from your goal of creating a working game, especially if you haven't a lot of experience finishing games. My advice is to wrap that stuff up in classes if it helps you get your game done. You might step on your own foot a few times, but you'll learn and for your next game you'll do it better. And you'll be three steps ahead of us who're on our third engine rewrite.

Share this post


Link to post
Share on other sites
Quote:
Original post by polymorphed
Quote:
Original post by McCoder
So sticking the WinApi Winmain in a class would be classified as "good"?


You can't do that, it won't work.
WinMainCRTStartup wants WinMain not SomeClass::WinMain.
It would be pointless anyway, since WinMain is just an entry point into the application. So in my opinion (I'm sure someone will disagree), it's not classified as a good thing.


So wether I like it or not, I always need to have a WinMain in my main.cpp file. Ugly, I guess it's pointless to have two WinMain's then.

Share this post


Link to post
Share on other sites
I dont, but even if I class WinMain, I still need a WinMain as the main entry point for the game right? Or can I do something like MyClass WinMain(1, 2, 3)?

Share this post


Link to post
Share on other sites
Well, yes you will need a free function of WinMain. But the point is that I cannot see why you would create a class for WinMain. What would it do? What members would it have? You could have a class for window creation maybe, but not the entry point itself.

Share this post


Link to post
Share on other sites
You must have a WinMain putting it in a class does not work but you can pass the control to you application class in the WinMain :


int WINAPI WinMain(...)
{
boost::shared_ptr<Application> myApp(new Application());
return myApp->Run();
}



The app class run method can call an init function then enter a message loop and handle the application.

Share this post


Link to post
Share on other sites
Quote:
Original post by smr


smr is right, although I don't agree with his conclusions. It is entirely useless to think for days on end about what the perfect design is—also known as Analysis Paralysis—if that doesn't result in a working game that's done before the deadline. You always have deadlines, even in amateur projects : you don't live forever, and your motivation is bound to decline a long time before you die anyway. So, always strive to strike a balance between design and accomplishment: too much design leads to no accomplishment, too little design leads to accomplishments that involve more rewriting old code than writing new code. However, if you've been there and done that and burned yourself with object-oriented design, the correct conclusion is not "object-oriented design sucks" but "I've been doing object-oriented design wrong".

I personally design on the fly, using the basic tenets of:
  • Use Idioms : the closer you stick to what is the "standard way of doing things" in your language/library, the more features of the language/library you will be able to use without any work.

    This obviously requires you to know what the idioms are, but it requires no design effort when you're writing code.

    Most people usually think merely of code style when I say this, but it covers a lot more. It also means that you should use the standard library containers whenever you need to represent mutable sequences and iterators for immutable sequences, that you should use RAII and respect the rule-of-three for constructor-assignment-destructors, that you should use constructors instead of factories whenever possible, that you should use references instead of pointers whenever possible, and so on.

    Derive your own idiomatic rules for your own language, if not C++ (I could rant about Objective Caml idioms for just as long).

  • You Ain't Gonna Need It : don't implement functionality unless you need it for what you're doing right now. If you don't know yet what you will need, your code will either be thrown away or will have an interface that's not adapter to how you need to use it.

    This is of course different from not writing code: code is cheap (easy to write, fairly repetitive, and compiler-corrected), functionality isn't (you need to decide how to do things, then test and debug). Glue code, for instance to respect idioms or to ensure extensibility down the line, is even cheaper.

    For instance, if you're writing a transform system, you already know ahead of time that several functions (rotate around arbitrary axis, for instance) can be implemented, but actually implementing them needs both intellectual effort and time. So, unless you have a clear example where the function will be used (and how it will be used) don't write it yet.

  • One Step at a Time : leaving one part of the program unfinished in order to complete another part means the first part will contain bugs (because it was implemented in two steps) and the second part will be highly coupled to the first.

    If you're writing some bit of code and suddenly find that you need another bit, there's a quick design question that you need to ask yourself : does this coupling (between the thing being implemented and the thing that is required) need to be strong ? Your choices are making the missing entity a parameter (that will be provided by the user of what you're currently writing) or writing a quick mock of the entity and globally referencing it. Depending on what the entity is, the choice will go towards parametric or global referencing. For instance, it's seldom advisable to keep global references to mutable data or anything that could be considered a configurable option.

  • Separate Responsibilities : if an atomic piece of functionality is bound to another piece of functionality (so that you can't use one without the other) then you need to separate them or you'll suffer down the line when one is needed without the other.

    The basic rule to determine this is to look at your design and look at every individual functionality, and decide whether that functionality can be used without any overhead (having to create objects or variables not directly related to that functionality). For instance, having to create and provide a texture manager (or a null pointer, by the way) to determine the number of enemies in a video game level is nonsense, meaning that the object or module responsible for accessing level data should not require a texture manager.

  • Once, Twice, Refactor : if you notice any repetition in your code, refactor so that the repeated code is repeated exactly once. The result will be code that is smaller (thus easier to understand and debug) and the refactoring will identify frequently reused semantic actions that may be useful (if you reused that code once, you will probably have to reuse it again).

    Don't be afraid, if you notice after writing a module that another design would have been much better, to either mention this in the design (as a comment) or to refactor it right away. The module should be small enough to allow you to refactor it within an hour.

  • Test First : automated unit testing serves two purposes. The first is that you are now free to manipulate code on a larger scale (such as massive refactorings) while still having a safety net that detects any mistakes you make.

    The second is that every module in your code now has two users. Several users means better debugging (you notice the errors earlier if more code uses what you just wrote) and also better design (you'll have less coupling, and you might notice awkward constructs while writing the tests).

    Almost everything in an application can be tested using unit tests, except the results of your API calls. If something seems difficult to test, this indicates that your code is difficult to reuse and lacks modularity, which will inevitably lead to issues down the line that are better solved right now.

  • Slim Down Your Interfaces : whenever you use an object in a function, think of the smallest set of functionality that is required from these objects. Anything you don't need shouldn't be required. This extends the range of situations where that code can be used.

    For instance, if a function is responsible for moving a camera around in the scene, that function has no reason to require that the passed object is a camera, because the fact that the argument is a camera is not used anywhere in the description of the functionality (aside from the arbitrary requirement that it should be one). The correct requirement here would be that the moved object should be an object which has a position within a scene (something which is the case for a camera, but also for most other objects, such as meshes or lights).


Classes are useful tools to achieve several of the above. If you know how to use them appropriately, then use them.

Share this post


Link to post
Share on other sites
ToohrVyk thanks for taking the time to write this down. It's very decent advice indeed, one thing though I did not understood. What do you refer when your speaking of automated large scale unit tests?

Share this post


Link to post
Share on other sites
Quote:
Original post by McCoder
What do you refer when your speaking of automated large scale unit tests?


Unit tests, test-driven development and game applications.

The basic idea is that you design your program functionality by functionality, for instance deciding that you want to code your enemies so that they fire bullets. So, you write some code which uses the enemies and checks that they can indeed fire bullets (meaning that they trigger the fire-bullet action and see if it did the right thing, and then tell you whether the test was successful or not). Once the testing code is written, you write the interface of the actual code to fire bullets (so that the code compiles, but the test fails because the bullet-firing function does nothing), which shows that your tests work. Then, you implement that function, and check that the tests still compile and also succeed.

Share this post


Link to post
Share on other sites
Quote:
Original post by McCoder
Looking at the flow of win32 api and opengl makes me wanna stick everything into classes. Is this a good practice? I would want to get so clean that I should read InitSound(); DrawStuff(); DiePainfully(); etc. Good clean code.


The arguement against classes has been around for as long as I can remember; it was probably a bit more valid back in the Pentium I days. Classes are probably one of the smaller speed concerns. Poorly written code (or poorly understood problem), as always, is a bigger concern.

Also, keep in mind that many of the APIs (Windows, MacOS, OpenGL?) were around before there were solid C++ compilers, and were also designed to be used from other programming languages (asm and pascal, for example).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this