• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
metsfan

One-Step vs Two-Step Initialization (C++)

33 posts in this topic

Hi all, I am currently in the process of making some critical decisions for my game's code, and deciding on paradigms that I am going to stick with throughout the project and I just wanted to get some opinions on this subject. As far as I can tell, there are positives and negatives to both of these methods. I've done some research around the internet, and it seems to be a pretty split decision about which practice is better in general, so I put the question to all of you, which practice do you prefer and use in your games, and do you differ the practice used based on situation?

For those who are not familiar with this terminology:

[b]One-Step Initialization[/b] - Constructor sets up the entire class and throws an exception on failure
[b]Two-Step Initialization [/b]- Constructors simply allocates the object and all initialization is done through a separate method, which will usually have a return value of a boolean or some error code.

Thanks all.
0

Share this post


Link to post
Share on other sites
I prefer the same. Do as much as possible in the constructor and throw an exception if anything goes wrong. If you use return values to indicate failure, you can be sure to forget to check it every once in a while and you will get unpredictable behaviour sooner or later. If you forget to catch an exception, you get at least a well defined shutdown of your application.
1

Share this post


Link to post
Share on other sites
This isn't a one-size-fits-all situation. There are many platforms with sufficiently bad exception support that even enabling exceptions as a compiler options is a mistake. Even if you do enable exceptions in your project, there are certain operations that have a sufficiently high chance of failure that throwing an exception would be inappropriate. Examples include opening a file or a network connection. Consider the C++ standard library. With a std::fstream you can specify a file name as constructor argument, but if the file isn't opened, it doesn't throw an exception. In comparison, if any of the std::string constructors fail an exception is thrown.
1

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1342418425' post='4959464']
This isn't a one-size-fits-all situation. There are many platforms with sufficiently bad exception support that even enabling exceptions as a compiler options is a mistake. Even if you do enable exceptions in your project, there are certain operations that have a sufficiently high chance of failure that throwing an exception would be inappropriate. Examples include opening a file or a network connection. Consider the C++ standard library. With a std::fstream you can specify a file name as constructor argument, but if the file isn't opened, it doesn't throw an exception. In comparison, if any of the std::string constructors fail an exception is thrown.
[/quote]The specific domain here is game programming, so the bad exception support platforms aren't likely to be used.

I don't agree with these examples of when not to use exceptions. If a critical resource, like a file is missing, that might be a good time to throw an exception.

Also a major consideration if an exception is appropriate is the cause of the error. If it is user error, then that's almost always not cause to use an exception. If it's a terminally missing resource, then throw one. Loading a missing save file -- don't throw. Corrupted save data - likely throw.

As an aside, one thing to watch out for is that you don't want to throw an exception in an exception handing routine. So don't make failure to log an error an exception, for example.
0

Share this post


Link to post
Share on other sites
i definately prefer 2 step, to me it makes code look nice, and it's more flexible on it's initialization and makes re-initializatioin even easier. And in c++ if you're old school you probably use memalloc,calloc, or what have you, which dont call constructors anyway.
0

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1342418425' post='4959464']
[b]This isn't a one-size-fits-all situation[/b]. There are many platforms with sufficiently bad exception support that even enabling exceptions as a compiler options is a mistake.
[/quote]For further elaboration. I've sometimes found myself to have the need to work with "non-trivial" ctors. Those kind of ctors that need object-specific behaviour by calling virtual functions.
Granted, I've mostly refactored those. But there are still a few cases in my current codebase where reliance on "virtual-based init" was the only possible way.
In this case, one-step init is just impossible.

[quote name='King Mir' timestamp='1342420868' post='4959475']
The specific domain here is game programming,[/quote]Let me rephase this for you.
[i]The specific domain here is jogging[/i] - where? city, offroad, mixed, gym. How? speed-oriented, long distances, cardio, weight loss...
0

Share this post


Link to post
Share on other sites
[quote name='Krohm' timestamp='1342422113' post='4959482']
[quote name='SiCrane' timestamp='1342418425' post='4959464']
[b]This isn't a one-size-fits-all situation[/b]. There are many platforms with sufficiently bad exception support that even enabling exceptions as a compiler options is a mistake.
[/quote]For further elaboration. I've sometimes found myself to have the need to work with "non-trivial" ctors. Those kind of ctors that need object-specific behaviour by calling virtual functions.
Granted, I've mostly refactored those. But there are still a few cases in my current codebase where reliance on "virtual-based init" was the only possible way.
In this case, one-step init is just impossible.

[quote name='King Mir' timestamp='1342420868' post='4959475']
The specific domain here is game programming,[/quote]Let me rephase this for you.
[i]The specific domain here is jogging[/i] - where? city, offroad, mixed, gym. How? speed-oriented, long distances, cardio, weight loss...
[/quote]There are indeed case where One-Step Initialization is not a good idea or even possible, but they should be the exception, not the rule.

As to the other point, I don't think see what you're trying to tell me. Yes game programming is a broad domain, but what I said about it still holds.
0

Share this post


Link to post
Share on other sites
[quote name='King Mir' timestamp='1342415710' post='4959452']
I would say one step, as much as possible. Exceptions make for cleaner code.
[/quote]

[quote name='rnlf' timestamp='1342417519' post='4959463']
I prefer the same. Do as much as possible in the constructor and throw an exception if anything goes wrong. If you use return values to indicate failure, you can be sure to forget to check it every once in a while and you will get unpredictable behaviour sooner or later. If you forget to catch an exception, you get at least a well defined shutdown of your application.
[/quote]

What makes you think that you can't have your "initialize" function throw an exception? The concept of return codes is just one of the two ways to implement two step Initialization. If you prefer one step initialization, great! But why? If you believe exceptions make for cleaner code, and throwing an exception from an initialize function is perfectly valid and typical of C++ exception styled programming, then you haven't made an argument for either yet. Poorly constructed exception handling is no better than poorly constructed non-exception handling either, so saying you have a "well defined shutdown of your application" is not always true.

What SiCrane said about "This isn't a one-size-fits-all situation." is pretty much the main focus point that should be addressed. Looking past exceptions, which is only one of the main aspects to this issue, the cost of "creation, copy, and destruction" have to be kept in mind as well. How expensive is it to create, copy, or destroy your objects? Are your objects going to need pools for managing memory? It's just going to depend on a case by case basis, thus there is no one-size-fits-all solution.

Furthermore, why choose one or the other when both might be more appropriate? Resource Acquisition Is Initialization, [url="http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization"]RAII[/url], is one such idiom where inherently it is one step initialization, but there are times when adding support for two step initialization can make for more flexible and simplified code. A specific example would be Win32 critical sections (whether you use them or not isn't important as much as understanding the justification). Rather than littering your code with Enter/LeaveCriticalSection calls, you can use RAII to greatly simplify the process. However, there might be times when you need the concept of RAII, but the implementation doesn't make logical sense. E.g., acquiring exclusive access to a CS, but not needing to maintain that access at all times (to prevent deadlocks), so allowing for two step initialization with manual cleanup code support helps keep things more simple/cheap than constructing/destructing new objects over and over.

It's typical in game development, and most other development, to make uses of 3rd party libraries at one point or another. Maybe it's a mysql++ connector wrapper, or a physics library, or an input or audio library, the list goes on. While a lot of people have a DIY mentality, it's important to understand that you can't simply reinvent the wheel for everything and will eventually have to resort to another person's code. A lot of these libraries might be setup to use exceptions, while some might not. In either case, you have to design your code around how the other components are setup.

This means, specifically to the OP, you should not be trying to "commit" to one style or another, especially in a language like C++. It's like saying you want to have dinner tonight, but don't want to eat a meal that would require the use of a fork. To each their own, of course, but it's certainly not a typical concern from that perspective to say the least.
1

Share this post


Link to post
Share on other sites
[quote name='Hodgman' timestamp='1342418795' post='4959465']
Game consoles [b]are the platforms[/b] that have bad C++ exception support. Microsoft/Sony tell you to disable exception support on their compilers in order to produce better binary code. Even x86/Win32 is pretty crap here, though x64 Windows is a lot better.
[/quote]

Even x64 Windows has had serious problems in its exception and critical error handling in the past (and present). One such example was described recently by Bruce Dawson on AltDevBlogADay:
http://www.altdevblogaday.com/2012/07/06/when-even-crashing-doesnt-work/
0

Share this post


Link to post
Share on other sites
Hm. Okay, I never had to deal with such broken platforms. If this is a serious concern, you're pretty much screwed anyway. Never noticed any of these problems on unix(-derivates). Didn't know, Windows would be such a deal breaker (again?).

If you only aim at Windows or consoles, maybe it may be worth considering not to use exceptions, but I'd rather hope for the vendors to fix their compilers (and operating systems) some day and risk sub-par performance until then.

BTW, the article seems to describe problems with exceptions generated by Windows, not exceptions generated by user code. Are there any known issues with user generated exceptions? What about non-Microsoft compilers? Edited by rnlf
0

Share this post


Link to post
Share on other sites
[quote name='CulDeVu' timestamp='1342440673' post='4959559']Even x64 Windows has had serious problems in its exception and critical error handling in the past (and present). One such example was described recently by Bruce Dawson on AltDevBlogADay:
[url="http://www.altdevblogaday.com/2012/07/06/when-even-crashing-doesnt-work/"]http://www.altdevblo...ng-doesnt-work/[/url][/quote]That's about Windows' [url="http://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx"]SEH[/url] ([i]which I mentioned earlier as being a good tool for dealing with crashes[/i]), not at all anything to do with C++ exceptions ([i]the try/catch/throw language feature that your quoted statement was about[/i]).

[i]N.B.[/i] these two different kind of exceptions live side-by-side. You can make use of SEH and C++ exceptions in the same program, or just use one or the other, or neither. SEH exceptions are raised by illegal operations that normally crash your program ([i]e.g. trying to write to a NULL pointer[/i]) or by calling the Windows [font=courier new,courier,monospace]RaiseException[/font] function. C++ exceptions are raised by the [font=courier new,courier,monospace]throw[/font] keyword.

[i]e.g.[/i] Despite my [i]personal choice[/i] to never use [font=courier new,courier,monospace]try[/font]/[font=courier new,courier,monospace]catch[/font]/[font=courier new,courier,monospace]throw[/font] ever in C++ ([i]I do use it in other languages where it's implemented better[/i]), I do choose to use [font=courier new,courier,monospace]__try[/font]/[font=courier new,courier,monospace]__except[/font] on Windows to generate crash reports and to propagate errors from worker-threads back to the main thread, at which time Bruce Dawson's articles are quite helpful ;) Edited by Hodgman
2

Share this post


Link to post
Share on other sites
[quote name='metsfan' timestamp='1342412041' post='4959446']
[b]One-Step Initialization[/b] - Constructor sets up the entire class and throws an exception on failure
[b]Two-Step Initialization [/b]- Constructors simply allocates the object and all initialization is done through a separate method, which will usually have a return value of a boolean or some error code.
[/quote]
I don't think this can properly be answered without asking: what can fail? and why can it fail? and when will it fail?

Things fail, but [i]where[/i] they fail is a matter of design (which you're considering now). However, I think you need to consider the above if you're ever to meaningfully decide [i]where[/i] something fails. I personally try to keep my initializations minimal so that failure in the constructor is both exceptional and fatal. "Actions" are more likely to fail (i.e. when the object actually [i]does[/i] something or "acts"), and I try to put these in member methods (not as "secondary initialization" functions but as logical, "acting" member methods).
1

Share this post


Link to post
Share on other sites
I hate this topic, before I was aware of it I was oh-so much happier.
Darn it. I liked my try-catch blocks they looked.. neat.
0

Share this post


Link to post
Share on other sites
[quote name='metsfan' timestamp='1342412041' post='4959446'][b]One-Step Initialization[/b] - Constructor sets up the entire class and throws an exception on failure[/quote]

My understanding was that this left the memory in a completely undefined state (please correct me if I'm wrong).

My method is to use the constructor to set built-in types to default values and pointers to NULL. Bigger operations are performed in separate functions that must be called explicitly. Edited by BinaryPhysics
0

Share this post


Link to post
Share on other sites
Classes that can fail construction is (to me) a fairly stinky code smell. Have sane defaults makes the code far easier to reason about, work with, test, and refactor.

In the rare case where the class [i]really needs[/i] some initial state provided in the constructor [b]and[/b] that initial state can have some bad value (null, NaN, etc) then just throw an exception there. If you're on a platform that doesn't support exceptions well... I have no idea. I've never had to deal with them; I'd be inclined to have a factory return the object or null.
1

Share this post


Link to post
Share on other sites
[quote name='BinaryPhysics' timestamp='1342457870' post='4959647']
[quote name='metsfan' timestamp='1342412041' post='4959446'][b]One-Step Initialization[/b] - Constructor sets up the entire class and throws an exception on failure[/quote]

My understanding was that this left the memory in a completely undefined state (please correct me if I'm wrong).
[/quote]Nope. Constructors throwing exceptions is well defined and safe. If an object you create holds a resource, then some care must be taken to insure that the resource is either acquired and released or not acquired at all, but that's easy enough if you wrap the resource in the smallest object necessary to handle it. For example, you would use two std::vector objects instead of managing two dynamic arrays in the same object manually.

[quote name='Drew_Benton' timestamp='1342438363' post='4959548']
[quote name='King Mir' timestamp='1342415710' post='4959452']
I would say one step, as much as possible. Exceptions make for cleaner code.
[/quote]
[quote name='rnlf' timestamp='1342417519' post='4959463']
I prefer the same. Do as much as possible in the constructor and throw an exception if anything goes wrong. If you use return values to indicate failure, you can be sure to forget to check it every once in a while and you will get unpredictable behaviour sooner or later. If you forget to catch an exception, you get at least a well defined shutdown of your application.
[/quote]
What makes you think that you can't have your "initialize" function throw an exception? The concept of return codes is just one of the two ways to implement two step Initialization. If you prefer one step initialization, great! But why? If you believe exceptions make for cleaner code, and throwing an exception from an initialize function is perfectly valid and typical of C++ exception styled programming, then you haven't made an argument for either yet. Poorly constructed exception handling is no better than poorly constructed non-exception handling either, so saying you have a "well defined shutdown of your application" is not always true.[/quote]The OP specifically mentioned using error codes instead of exceptions in two phase initialization. It doesn't make sense to use two phase initialization when there is no explicit benefit. Yes it allows virtual dispatch and returning an error instead of an exception, but when these features aren't used, then there's no point to it. In the absence of any other consideration I would say one phase initialization is better because it's less code, and less places where an error can occur. These reasons are why I suggest using one pass initialization in the general case. I do agree that it's not "one size fits all" though.

As an aside virtual dispatch for initialization can also be achieved by factory objects.
0

Share this post


Link to post
Share on other sites
Game initialization is usually multi-pass. Most games these days are not simple enough to do all their initialization within a single constructor pass. Sometimes u want to create objects in memory and push in saved state and then do some pointer fix up, such complex multi-pass initialization schemes preclude doing it all in the constructor. My games are multi-pass, there are object pools, script based multi-state initialization, soft reset, etc.. But don't over-engineer, if your game can get away with a single pass constructor go for it. As others have said its really dependent upon the requirements. Good Luck! Edited by ddn3
0

Share this post


Link to post
Share on other sites
I switched to one step init using constructors a couple of years ago and never looked back. I don't use exceptions (in C++) because in my games if I need something it means I really need it to keep the game going so if something goes bad I just crash with a call stack log and go fix the bug. The rare things that might fail and can be recovered (ie, an optional data file that is not there) get pre-tested then constructed if the situation allow them to be constructed.
But the rule is, right after an object is constructed it has to be ready to go and fully initialised... I have the feeling this has incredibly decreased the amount of runtime crashes in my software because construction order and object relationships are much more well planned.. no init( bla, bla) , no setMyImportantPointer( bla* bla ).. if one object needs another one to be fully constructed the relationship is explicitly expressed in the constructor.. so everything is forced to be top-down.
2

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1342418425' post='4959464']Consider the C++ standard library. With a std::fstream you can specify a file name as constructor argument, but if the file isn't opened, it doesn't throw an exception. In comparison, if any of the std::string constructors fail an exception is thrown.[/quote]

That was not a design decision, it was because the iostreams was written before exceptions were added to C++. You can enable exceptions in iostreams by calling the istream::exceptions() or ostream::exceptions() method.

And I wouldn't refer to the iostreams library for any kind of design advice. It's not exactly a shining example of C++ engineering.
0

Share this post


Link to post
Share on other sites
Forgetting about the exception part "code safe" vs "fast code & stable" phylosophy & practices being discussed, I would like to raise a point related with building an actual game:

Your game will eventually get to the point where a level is restarted (may be because the player died), or a full game session was restarted. Sometimes the one-step approach works fine, because objects from the previous level/session get destroyed and recreated.
In fact I use this a lot (minus exceptions, I don't use them)

But it may be posible that some of these objects must, for some reason, stay persistent throughout levels or even game sessions. So, you can't delete them and create new ones, but you'll probably need to reinitialize most of it's variables to a default value.
For these cases a two-step approach is much better suited, where you just call init( bool firstTime ) rather than having to refactor everything or resort to copy-pasting the constructor into a reinit() function, then debug why reinit is failing (because you pasted more code than you should, or forgot to copy something).
I also use this two-step approach when suitable.
Furthermore the multi-step initialization design adapts easier for multithreading (leave the concurrent part in one or more passes, execute the serial code in another pass).

Just food for thought. Edited by Matias Goldberg
0

Share this post


Link to post
Share on other sites
[quote name='Cygon' timestamp='1342465410' post='4959681']
That was not a design decision, it was because the iostreams was written before exceptions were added to C++.
[/quote]
C++ went through a process of standardization where features were added to the language and things like the CFront library designs were changed in response to those feature changes. The new versions of the standard library were given extensionless header names and the old CFront style implementations continued to be available for a while from most compilers in the form of the .h header files. Ex: iostream vs. iostream.h, fstream vs. fstream.h, etc. There are actually some interesting differences in the interfaces between the old iostream library and the newer iostream library, such as the removal of the noreplace and nocreate flags. Another is that the old-style iostream library gave istream and ostream protected constructors. To extend the library you would derive from istream or ostream, and in your constructor use the default constructor for the base class and call the init() function from the base class. Whether or not you believe the interface of the iostream portion of the standard library to be a good design decision, it was nonetheless an actual design decision.
0

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  
Followers 0