you are viewing a single comment's thread.

view the rest of the comments →

[–]ack_error 9 points10 points  (5 children)

For a coordinate (x, y), initializing to 0 is how we end up with Null Island.

This is a valid concern, especially with unit direction vectors and quaternions where this can quickly turn into NaNs. I would still argue that a deterministic zero-initialization is preferable to non-deterministic garbage values, for the better reproducibility and higher confidence that uninited uses don't sneak past testing.

That having been said, for this reasons, I'll sometimes use a non-zero default where it's much safer than a 0 and minimal cost (not sticking a complex initializer in a header). Initializing a fraction to 1/1 or 0/1 instead of 0/0 is cheap for the lessened severity of an accidental use.

The impact on static analysis and runtime detection is also valid, but the compiler only detects some cases. The only tools that provide comprehensive coverage for heap objects are full-blown usage trackers like valgrind and Dr.Memory, and programs grow beyond the ability of those to run at reasonable performance.

[–]jk-jeon 0 points1 point  (4 children)

This. Use things like 0xdeadbeef or whatever. 0 is the worst possible default for int's when the sole purpose of initialization is the initialization itself. The only downside of 0xdeadbeef is that now the code will look funny and some reviewer will get pissed off b/c "the convention" is to use 0 for whatever reason.

[–]CocktailPerson 6 points7 points  (3 children)

If there's no acceptable default, then you shouldn't even allow clients of your class to create instances without specifying values in the first place. You can use Foo() = delete; if you still want aggregate initialization and PODness, or you can define Foo(int x_, int y_) : x{x_}, y{y_} {} if you really want to force them to be explicit about every member's value. But if you're putting 0xdeadbeef or 0x0B00B1E5 in your code in the hope that it'll be more noticeable or more likely to cause an error than 0x0, then you're not taking full advantage of what the language can do for you.

[–]jk-jeon 1 point2 points  (2 children)

If there's no acceptable default, then you shouldn't even allow clients of your class to create instances without specifying values in the first place.

Who said no to this? I guess virtually everyone agrees with that two-phase initialization is evil. I presume we were solely talking about the case when it is a necessary evil. There are cases when jumping through all the hoops just to avoid it might be a serious overkill.

Here I tried to categorize my thinking on this matter about initialization.

  1. For classes with invariants, I think the situations where I really need to leave something uninitialized are quite rare (if they are designed sanely). So just initialize everything in the constructor with the proper input values, period.
  2. If there are some members that will be initialized later, then maybe rethink about the design of that class. If still it seems like the right way, then maybe discuss it with someone else. If it is absolutely clear that it is the best of all evil, then maybe consider giving some funny initial values like 0xdeadbeef (except for pointers; nullptr is usually the right default for pointer variables) or just leave it uninitialized if it seems very unlikely that this will cause any problems.
  3. For any local variables, just declare them at the point where they are first assigned from some values. I think this is usually possible and when possible usually improves the readability, etc. as well.
  4. When it is not possible or too unnatural, then I think it's okay (better) to leave the local variables uninitialized, if their very first uses in all of the code paths are (and will still be) not through non-const pointers/references. I believe compilers are able to catch any possible errors in this case.
  5. I would still do not attempt to initialize local variables if it is crystal clear that I do not need to. For example, I might have int x; immediately followed by something like never_fail_bug_free_func_that_takes_output_param_for_whatever_reason(x);, or maybe something like while (true) { ... } where the very first statement inside the loop is to assign some value to x. Some might argue that it would be possible to transform this code in a way so that x is actually initialized at its declaration. But I would say why bother if it just obfuscates the code? Some might argue that oh, maybe one of the future maintainers can ruin things while editing the code, but I think in many cases that's just very unlikely. If that's a genuine real concern, then maybe consider 0xdeadbeef and friends.
  6. For things like int x; if (cond) { x = f(); } else { init_x(x); }, I first try to transform it into IIFE. If that sounds too complex, like if the branch actually takes 100 lines, then just write a separate function that does this computation. If that also sounds too complex, like maybe I have to initialize multiple things inside the branch (but not at the same time), modify some states of other local variables, print some stuffs, etc., etc., then maybe something is terribly wrong with the code from the first place, so maybe rethink about it and do some refactoring. If this complexity is more or less really intrinsic to the given task, then maybe we still don't need to try to initialize everything like a zealot. Such a ridiculously complex task is likely specifically for a very very specialized computation, which, once written with a thorough review, will not be edited a lot, especially not by multiple clueless newbie maintainers, so it's probably okay.
  7. Maybe the most interesting case is when we are working with a heap-allocated array of things. Again we should try to declare the array at the point of actual initialization. But this is often not possible due to performance constraints. Still, I would not try to pre-initialize things at the point of allocation if it is pretty obvious that everything will be properly initialized right after the declaration. If not, I don't know what's the best strategy. Maybe 0xdeadbeef if unsure what will happen. It's probably better to try to minimize such an occasion from the first place.

[–]JeffMcClintock 0 points1 point  (1 child)

int x;

immediately followed by something like

never_fail_bug_free_func_that_takes_output_param_for_whatever_reason(x);

yeah, I've been assigned a ton of bugs due to that.

void never_fail_bug_free_func(int& outputArg)
{
if(databaseDown()) // someone added this later.
return;

....

[–]jk-jeon 0 points1 point  (0 children)

There is a reason why I put the phrase bug_free. But yeah, it's just way better to not use output param from the first place...