• 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
japro

Why am I only told what not to do

14 posts in this topic

If you're working by yourself, write code however you want. There's no reason not to. If it works, it works. If you run into any major design problems, you'll gain a better understanding of the problem. You'll also understand your entire codebase so you will be able to fix it more easily than if someone else wrote it.

On your own projects, you should intentionally experiment with "weird" or "creative" program design. Chances are you'll come up with something that's already been made elsewhere, but more importantly you'll understand a wider variety of software architectures.

On a team, it's more important to figure out and follow the guidelines everyone else on the team is using. If everyone on a team codes in totally different ways, there's inevitably a lot of "WTF is going on here?!" moments. Generally you'll be on a team for any paid projects, and paid projects are always under time constraints (which means if you screw up the design the first time, you may not have a chance to fix it).
1

Share this post


Link to post
Share on other sites
I'm assuming you're referring to [url="http://altdevblogaday.com/2011/07/25/the-game-entity-part-ii-the-life-cycle-and-processing-architecture/"]this article[/url]? I also noticed that sentence a few days ago and felt similarly crushed - if one of my most basic assumptions, something I took for granted turns out to be bad practice then where am I really? Good to know it was a false alarm. It may just be poorly worded (or ironic?), though I have no clue how it was meant to read.

If you're interested in component/entity systems specifically, I can point you to post #51 by Lord_Evil, buried in [url="http://www.gamedev.net/topic/463508-outboard-component-based-entity-system-architecture/page__st__50"]this thread[/url]. After weeks of looking in vain at various threads and articles, this was the one approach that I personally understood, liked and was able to implement. It might be of some use to you.
0

Share this post


Link to post
Share on other sites
Because a lot of bad things are bad everywhere. Very few good things are good everywhere. They're good if you're in this scenario, with these requirements, with those developers... and even then there's trade offs.

Personally though, I dislike non-trivial constructors. They're problematic in design (near impossible to reuse/abstract, have many touchpoints so are hard to refactor, have limited options for graceful failure), difficult to test effectively, and 'sane defaults' (in my experience) allows you to reason about a program and work with the class much easier. If you're enforcing some invariant (like constructors are meant to/are good at) then by all means make a non-trivial constructor (even if that involves allocations) (imo).


And you're likely not a horrible programmer. If you learn from your coding enough to look at others' opinions with a critical eye, that's pretty much the definition of a good programmer.
0

Share this post


Link to post
Share on other sites
[quote name='ApochPiQ' timestamp='1312590880' post='4845289']
For what it's worth, the idea that you shouldn't allocate memory in a constructor is ludicrous.
[/quote]

As evidence of how implausible it is that this is good advice, many objects that are part of the standard library (std::string, std::vector...) do provide constructors that allocate memory.

So here's a positive-style piece of advice: Write classes that behave in ways similar to the classes provided as part of the standard library. And here's an explanation to go with it: If you follow my advice, any developer that is familiar with the language will feel comfortable using your classes.
0

Share this post


Link to post
Share on other sites
[quote name='alvaro' timestamp='1312818670' post='4846224']
As evidence of how implausible it is that this is good advice, many objects that are part of the standard library (std::string, std::vector...) do provide constructors that allocate memory.[/quote]

Standard library treats memory allocation as orthogonal by using allocator concept. Entire operation of the library handles all resource allocations via external interface, which falls in line with original advice. While operator new can be overloaded, the default versions do not pass in context in which they are called, suffering from typical global namespace issues.

When designing APIs, the biggest mistake is to internalize resource allocation. In C, allocator function should be provided. In C++, allocator. In Java or C#, factory interface. Dynamic languages do not suffer from this as much since any call can be mixed-in or modified at any point to insert cross-cut functionality.


The lesson here is rarely emphasized enough, but resource life cycle is absurdly complicated problem. By including it into some library or algorithm it creates such rigid design that it may be rendered useless for any further development, either through reuse or through feature changes. Resources here can range from memory to files but even to number of rows in database, number of URLs supported or lines in a file.

FactoryStrategyFactorySingletonProvider is somewhat of a joke in Java world. Admittedly, Java is a clumsy and verbose language, but the problem is real. Di/IoC is in many ways the worst of all worlds, but it emerged as only scalable (in terms of development) methodology. Key lesson is to decouple resource creation/allocation from business logic/rule engine. It really is that important as projects grow.

And as said, dynamic languages do not suffer from this problem, even though at surface they appear to be allocating objects left and right. Focus is on resource allocations and memory in most languages and problems isn't important enough. C and C++ tend to be used precisely for this reason, so memory should be treated as one of resources that needs to carefully managed.
1

Share this post


Link to post
Share on other sites
[quote name='japro' timestamp='1312584860' post='4845260']
Hi,

So maybe this is some sort of selective perception thing but I'm getting somewhat annoyed that software design articles/discussions and such seem to be mostly about what not to do. Were I to follow all the "advice" about bad practice I wouldn't write code at all.
So I was reading the "Demise of the game entity"-thread and while I agree with the problems described there and encountered them myself, no one is actually suggesting concrete solutions. Then I went on to read the articles the thread references to and right at the beginning I find this:
[quote]I have encountered some architecture’s that have banned the use of Constructors and Destructors in game entities! Usually because of a misunderstanding of how C++ works and usually backed up by the fact that some engineers just [b]can’t stop themselves from doing memory allocations and other nonsense in the constructor[/b].[/quote]
When did allocating memory in a constructor become a bad idea?

I always feel stupid when I encounter this kind of statement, since the apparently competent authors are dropping these things like it was completely obvious that this is nonsense, but it isn't to me. Does that mean I'm a horrible programmer? And again, I'm told what not to do, but not what I should do...

(sorry, if this is kinda rantish)
[/quote]

You hit upon two pervasive issues of confusion.
The first is that constructive criticism is hard work - I would hope that most articles say don't do it this way or that way and then present an acceptable, ideal, or optimized method.
e.g. It's easy to say some aspect of a design is undesirable. It is quite difficult to provide a universally better approach. Adding complexity to address rare conditions or marginal concerns is not a good idea.

The more specific issue about memory allocation in constructors is mostly about determinism (which is a lacking quality in software today).
The difficulties of testing it is rooted in the lack of determinism.

Consider this contrived example,
[code]
class Exploder
{
int* all_good;
int* death_to_all;
public:
Exploder()
{
all_good = new int[1000];
death_to_all = new int[1<<31];
}
}
[/code]

If the ctor throws... the dtor is [b]not[/b] invoked.
I think the only way to handle this is zero all members prior to performing any allocations (inject determinism, we now know they are all zero prior to the possibility of an exception thrown), then catch any exception thrown, perform clean-up on non-null members, then rethrow the exception. (This is a pain-in-the-ass so no one does this.)
If you start new'ing in the ctor you may become tempted to call other functions in the ctor. Perhaps even a virtual function in the ctor which will not invoke the current class's implementation but, rather, the base-class implementation because 'this' class does not exist until the ctor successfully completes (so the vtable cannot be trusted). In C++ I believe this is technically undefined behavior, there might not [i]be[/i] a base-class implementation. I would assume in Java and certainly in C#/.Net there are additional keywords that clarify and eliminate this issue (you get a compiler error instead of crashing at run-time.)

The more general problem this touches on is one I call "granularity" (it also touches "Conceptual Integrity" but everything touches CI).

Our Conceptual Integrity goal is to make our entire program adhere to RAII without requiring the programmer to resort to extraneous tactics such as a two-staged atomic object initialization consisting of a zeroing phase followed by a allocation phase combined with lots of catch/rethrow blocks to clean everything up.

To have good CI it has to be straight-forward.
What we need is a consistent rule that we can follow that will produce correct code.

In C++ there is a concept known as RAII (resource-acquisition-is-initialization).
An example simple rule is you are not allowed to use 'new' in a constructor unless you are creating a primitive object that contains one, and only one, dynamic data member.
Primitive objects must be RAII compliant.
All other objects are now composed of primitive objects which automatically makes them RAII compliant.
e.g. replace new int[] with a vector.resize().

Another simple rule is object-model classes (classes part of your big-picture design) are not allowed to use RAII.
Only low-level utility classes are allowed to use RAII.
(Why I call it a granularity issue.)

Or even, Only use RAII if there is no other choice.
For tasks such as [exception] safely acquire and hold a mutex, this is the "only" choice.
2

Share this post


Link to post
Share on other sites
[quote name='Shannon Barber' timestamp='1314328926' post='4853914']
[quote name='japro' timestamp='1312584860' post='4845260']
Hi,

So maybe this is some sort of selective perception thing but I'm getting somewhat annoyed that software design articles/discussions and such seem to be mostly about what not to do. Were I to follow all the "advice" about bad practice I wouldn't write code at all.
So I was reading the "Demise of the game entity"-thread and while I agree with the problems described there and encountered them myself, no one is actually suggesting concrete solutions. Then I went on to read the articles the thread references to and right at the beginning I find this:
[quote]I have encountered some architecture’s that have banned the use of Constructors and Destructors in game entities! Usually because of a misunderstanding of how C++ works and usually backed up by the fact that some engineers just [b]can’t stop themselves from doing memory allocations and other nonsense in the constructor[/b].[/quote]
When did allocating memory in a constructor become a bad idea?

I always feel stupid when I encounter this kind of statement, since the apparently competent authors are dropping these things like it was completely obvious that this is nonsense, but it isn't to me. Does that mean I'm a horrible programmer? And again, I'm told what not to do, but not what I should do...

(sorry, if this is kinda rantish)
[/quote]

You hit upon two pervasive issues of confusion.
The first is that constructive criticism is hard work - I would hope that most articles say don't do it this way or that way and then present an acceptable, ideal, or optimized method.
e.g. It's easy to say some aspect of a design is undesirable. It is quite difficult to provide a universally better approach. Adding complexity to address rare conditions or marginal concerns is not a good idea.

The more specific issue about memory allocation in constructors is mostly about determinism (which is a lacking quality in software today).
The difficulties of testing it is rooted in the lack of determinism.

Consider this contrived example,
[code]
class Exploder
{
int* all_good;
int* death_to_all;
public:
Exploder()
{
all_good = new int[1000];
death_to_all = new int[1<<31];
}
}
[/code]

If the ctor throws... the dtor is [b]not[/b] invoked.
I think the only way to handle this is zero all members prior to performing any allocations (inject determinism, we now know they are all zero prior to the possibility of an exception thrown), then catch any exception thrown, perform clean-up on non-null members, then rethrow the exception. (This is a pain-in-the-ass so no one does this.)
If you start new'ing in the ctor you may become tempted to call other functions in the ctor. Perhaps even a virtual function in the ctor which will not invoke the current class's implementation but, rather, the base-class implementation because 'this' class does not exist until the ctor successfully completes (so the vtable cannot be trusted). In C++ I believe this is technically undefined behavior, there might not [i]be[/i] a base-class implementation. I would assume in Java and certainly in C#/.Net there are additional keywords that clarify and eliminate this issue (you get a compiler error instead of crashing at run-time.)

The more general problem this touches on is one I call "granularity" (it also touches "Conceptual Integrity" but everything touches CI).

Our Conceptual Integrity goal is to make our entire program adhere to RAII without requiring the programmer to resort to extraneous tactics such as a two-staged atomic object initialization consisting of a zeroing phase followed by a allocation phase combined with lots of catch/rethrow blocks to clean everything up.

To have good CI it has to be straight-forward.
What we need is a consistent rule that we can follow that will produce correct code.

In C++ there is a concept known as RAII (resource-acquisition-is-initialization).
An example simple rule is you are not allowed to use 'new' in a constructor unless you are creating a primitive object that contains one, and only one, dynamic data member.
Primitive objects must be RAII compliant.
All other objects are now composed of primitive objects which automatically makes them RAII compliant.
e.g. replace new int[] with a vector.resize().

Another simple rule is object-model classes (classes part of your big-picture design) are not allowed to use RAII.
Only low-level utility classes are allowed to use RAII.
(Why I call it a granularity issue.)

Or even, Only use RAII if there is no other choice.
For tasks such as [exception] safely acquire and hold a mutex, this is the "only" choice.
[/quote]
0

Share this post


Link to post
[quote name='Shannon Barber' timestamp='1314328926' post='4853914']
If the ctor throws... the dtor is [b]not[/b] invoked.
I think the only way to handle this is zero all members prior to performing any allocations (inject determinism, we now know they are all zero prior to the possibility of an exception thrown), then catch any exception thrown, perform clean-up on non-null members, then rethrow the exception. (This is a pain-in-the-ass so no one does this.)
If you start new'ing in the ctor you may become tempted to call other functions in the ctor. Perhaps even a virtual function in the ctor which will not invoke the current class's implementation but, rather, the base-class implementation because 'this' class does not exist until the ctor successfully completes (so the vtable cannot be trusted). In C++ I believe this is technically undefined behavior, there might not [i]be[/i] a base-class implementation. I would assume in Java and certainly in C#/.Net there are additional keywords that clarify and eliminate this issue (you get a compiler error instead of crashing at run-time.)
[/quote]

You got the C# part wrong there is no keyword to do this in C#, it allows you to call virtual functions from the constructor. C# constructs from the derived type downwards so the vtable exists as soon as you hit the constructor body.

And to extend your point about if the constructor throws the destructor is not invoke this is valid for any failing constructor regardless of it throwing or not, it it fails it will not call it's destructor. You have just created a zombie object that will keep up taking memory but won't ever be released until program termination. And one of the easiest ways to generate this is whit allocations in the constructor, thats the reason why people insist on banning allocations in the constructors.

0

Share this post


Link to post
Share on other sites
So basically the article suggests using a bool sometype::init() instead of the constructor object for allocating resources as init can fail gracefully and constructors will just create massive mem leaks. I actually agree 100% with the article on this issue and would like to suggest http://www.scs.stanford.edu/~dm/home/papers/c++-new.html as a good read on the subject of constructors
-1

Share this post


Link to post
Share on other sites
[quote name='Lewis_1986' timestamp='1314856495' post='4856149']
So basically the article suggests using a bool sometype::init() instead of the constructor object for allocating resources as init can fail gracefully and constructors will just create massive mem leaks. I actually agree 100% with the article on this issue and would like to suggest [url="http://www.scs.stanford.edu/%7Edm/home/papers/c++-new.html"]http://www.scs.stanf...rs/c++-new.html[/url] as a good read on the subject of constructors
[/quote]
I think multi-step construction has too many disadvantages. If you really need complex building, maybe builder pattern could do the job. Pass the builder object around until the real object can be safely constructed.

That rant you linked is wrong on so many levels I'd "unlike" your post if I could. It is referring to gcc 2.8.1 so I guess it is written around 1998.
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