C++ Constructors vs. Init() Functions

Started by
43 comments, last by nobodynews 10 years, 1 month ago

Uhm, that's bad news for me. I've spent quite some effort in making my design more modern, mostly because...

The job of writing C++ code is made a lot easier if you can just assume that no function will ever throw...

... but there is the benefit of being able to assume no function can fail.
Throwing an exception is a method to indicate a failure.
Returning an error code is a method to indicate a failure.

While both indicate failure, the methods, costs, benefits, risks, and rewards of the two are radically different. The pros and cons of each have been discussed many times in different topics already.

For this discussion it is enough that the console game industry has rejected c++ exceptions as a method of indicating failure. Collectively they have decided that within the niche industry the costs of c++ exceptions outweigh the benefits.

If you want to start a new topic on it (AFTER searching the forum to read the previous flame wars, debates, and discussions) it would probably be better than redirecting this discussion on constructors versus utility initialization functions.
Advertisement

But that's just C without the ++, what's the point of having all of these fancy features if you can't use them? I'd also think that writing your own containers is more error prone. Then again, I was never affiliated with the gaming industry, I'm sure these decisions are for the best.



10 years ago is a long time. C++ compilers weren't so great back in the day, especially on console's so those older games were probably essentially C with Classes. More modern C++ without std containers or exceptions with custom allocators is very powerful though and won't look very much like C code with inheritence, polymorphism, and templates.

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

But that's just C without the ++, what's the point of having all of these fancy features if you can't use them?

You misunderstand.


Look harder at those four things. These are the same Items I have seen blocked in studios and projects as well:

* don't use the standard new/delete/malloc/free (use some engine-specific replacement / wrapper)

The default allocators are SLOW. The default allocators struggle under many situations that game consoles require, such as alignment requirements beyond those found in the language standard; the language standard allows for addressability, not for hardware like cache-optimal and GPU-preferred alignments . Most game engines and systems provide significantly faster replacements that also provide options for non-standard alignments and other features. The global allocation system also has poor debugging support; many industries (not just games) replace the allocators with more debug-friendly memory libraries.

It is not a fear of the language, or some abstract concerns about compilers. It is because the allocation system offered in the standard library is insufficient for the needs of the community.


* don't use std containers

Most major studios provide implementations based on STL Port that are designed around better allocator methods or different memory strategies that do not resize and restructure as readily, and that are implemented in less-generic terms in order to achieve easily measured performance improvements. You might find this document rather enlightening although it represents how things were seven years ago; EASTL specifically is a living system and has incorporated C++11 and also proposed C++14 features. This type of library is common at studios, and is frequently requested at smaller studios.

It is not a fear of C++ or a lack of understanding, but instead it is because the general purpose standard library functionality is insufficient for the industry generally.

* don't use exceptions
* don't use RTTI or dynamic_cast

Both of these fall under an interesting bucket. In C++ these two features are unique in that you pay for them no matter what, even if you don't rely on them. Enabling the features expands the size of your executable (consuming more extremely valuable memory) and consumes cycles as the program runs (harming performance). It is because of actual, well-documented, studied, and debated reasons that most game consoles have these features off by default, sometimes not even bothering to implement them on the machine.

There are few areas where exceptions are required in the language standard. Most of them are to inform the programmer of passing bad parameters our out-of-bounds parameters. These are bugs that should all be caught during development, and if they do manage to escape into the wild, should default to a sane and safe alternative value. The other place they are used is to indicate certain unrecoverable conditions, like failure in allocating memory. Again, this is a situation that should be avoided and is something that should be carefully managed through other means on a limited-resource game console. Coupling all the reasons together, especially the fact that less costly alternatives exist, these are typically not present in console games.


It has nothing to do with game programmers wanting to prove themselves by rewriting containers, or denying fancy features, wanting to live in the past, or refusing to learn the new language. It has everything to do with the c++ language providing generic support that is generally quite good, but in a few instances inadequate or inappropriate for this specific industry.


Trying to bring this back toward the original topic...

This is also why so many systems rely on constructors doing minimal work, creating empty objects, or having multi-step composition of objects; when you are in a situation where individual cycles count, it is generally best to give the programmer freedom to perform those steps at a time they choose, rather than compelling it during construction. Don't force them to follow a model if it doesn't fit.

...if the allocation of floatPtr throws an exception (and I am not sure how saying "I don't use exceptions" can help you here), the destructor of B will not be invoked and A will leak.

You can avoid that by using "no throw new" instead of "regular new" (or by using the compiler command like options to disable exception support). This is one reason why large projects often ban 'new' and force you to use a macro like MY_NEW (e.g. Then they can make all allocations provide the no-throw guarantee).

On the topic of games, if you run out of RAM, you pretty mic just want to crash with a good debugging dump, and then fix your code :)

Also, if you choose not to use exceptions, you'll have no catch statements at all, so any use of throw at all will end up bubbling to main and aborting your app ;-)

I am not going to claim that all the code I write has strong exception-safety guarantees, but this common problem is fixed by always wrapping each dynamically-allocated member in a class that guarantees exception safety (a smart pointer would do, or something like class A in Hodgman's example). You simply don't write classes that hold two raw pointers to dynamically allocated data, and you don't write mixed classes like class B above.

thats the "weak exception safety" guarantee, which should be mandatory if you're choosing to use exceptions in your project.

But that's just C without the ++, what's the point of having all of these fancy features if you can't use them?

You misunderstand.

I didn't say that! :)

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

This topic is closed to new replies.

Advertisement