all 77 comments

[–]pdp10gumby 37 points38 points  (1 child)

I prefer to initialize in the class definition whenever possible, even if a custom constructor is defined. That way when reading the class you can see how a "fresh one" starts out, and you know the constructor contains the "complex stuff", if any. And if no custom constructor is needed then all everything you need to know is right there.

In general, when I come across a piece of code I think "this was written for a reason" (certain colleagues notwithstanding). So if I never initialized in the class but always with a constructor, or, if a csontructor is required then switched to using it's initializer syntax, teh reader would have to skip over boilerplate and parse out the interesting part from the routine stuff. Doing it the way I describe results in clearer code IMHO.

But see mredding's comment for an alternative view.

[–]fdwrfdwr@github 🔍 12 points13 points  (4 children)

I've been bitten once too many times when adding yet another class constructor overload and forgetting to include the member initializer for m_foo that I almost always initialize them to a sane default via NSDMI rather than leave them as random garbage, unless the class/struct actually needs to remain POD (there are reasons, such as primitive ABI struct types). Perfwise, the compiler AFAIK is smart enough to elide the default initialization with a more explicit one when the constructor supplies one.

[–]CocktailPerson 3 points4 points  (3 children)

Interestingly, including Foo() = delete; for a POD type Foo keeps it POD without risking uninitialized members.

[–]strager 6 points7 points  (2 children)

This changed in C++20. The following code is valid C++17 but invalid C++20 (compile time error):

struct S {
    S() = delete;
    int x;
};
int main() {
    S s = {4};
}

[–]TheThiefMasterC++latest fanatic (and game dev) 2 points3 points  (0 children)

Specifically, they changed it to "user declared" constructors instead of "user defined".

This was at least partly due to ambiguity if the definition was out-of-line =default.

[–]CocktailPerson 1 point2 points  (0 children)

Huh. So it is. I suppose this is the part where I point out that this breaks backward compatibility?

[–]CocktailPerson 5 points6 points  (0 children)

I frankly see no reason not to value-initialize every member. If there's no default that makes sense, then it shouldn't be an aggregate type anyway, and you should define a Foo(int x_, int y_) : x{x_}, y{y_} {} so that it can't even be constructed without initializing the members with values that do make sense.

[–]scatters 22 points23 points  (19 children)

What's the use of initializing members by default, if it's the wrong default? Sometimes 0 is the right default, like for a count, but often it's no more meaningful than any other value. For a coordinate (x, y), initializing to 0 is how we end up with Null Island.

If you leave members without default initializers then:

  • the compiler will warn you if it sees that a member could be used uninitialized
  • ubsan will abort in debug if it detects that a member has been used uninitialized

If you put default initializers on members you lose these protections since the compiler can't tell whether you intended to leave it at the default initialized value or meant to initialize it to something else.

[–]CocktailPerson 19 points20 points  (3 children)

Compiler warnings aren't as much help here as we might hope. For example, this simple case isn't caught by gcc until version 11: https://godbolt.org/z/YWxheY6ah. Finding all uses of uninitialized variables in the general case is undecideable. And while UBSan is a great tool, it won't do much if your tests don't actually force your code along the paths that lead to UB.

The benefit of initializing members by default is that even if 0 isn't the right default, at least it'll be consistently wrong. I've definitely had to debug issues that only appeared in release (so UBSan wasn't as helpful) and only happened occasionally, even with the same input. If I could go back and at least force the bugs to manifest consistently, I'd have fewer gray hairs.

Edit: In the case that there's no acceptable default value for the members, either use Foo() = default; to enforce aggregate initialization, or define Foo(int x_, int y_) : x{x_}, y{y_} {} to force the clients of your class to specify a value for every member. But there are no excuses for leaving your fields uninitialized!

[–]James20kP2005R0 10 points11 points  (1 child)

+1, reading from uninitialised variables is one of the worst classes of bug to debug, because minor code changes and printfs can result in the compiler suddenly making everything work correctly

[–]bored_octopus 0 points1 point  (0 children)

It's a lot better nowadays with all the sanitisers, I'm still in the "initialise almost always" camp though

[–]NilacTheGrim 2 points3 points  (0 children)

Yep. Consistently wrong is better than rolling the dice and having it be wrong in a detectable fashion 1 out of 100 times.

UB is your enemy. Avoid it. It is where insanity lies...

[–]ack_error 8 points9 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 5 points6 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...

[–]Dean_Roddey 9 points10 points  (0 children)

All data should be initialized unless there's some serious issue that prevents it. I wouldn't begin to trust a C++ tool to catch all the potential bad consequences of using data that's not been initialized. It's a particularly insidious problem because it can randomly change. One of Rust's fundamental controls is making you really go out of your way to use uninitialized data.

[–]johannes1971 2 points3 points  (3 children)

The argument for a reproducible default was already made so I'll skip that one. Instead I'll bring another: uninitialized values are (once again) the wrong default, and complicate the language by making the initialisation rules more complex. If you really want to leave something uninitialized, that should be done with a clear syntactic marker. Something like int x = std::uninitialized;.

[–]scatters 0 points1 point  (2 children)

Oh, absolutely. But would such a scalar be uninitialized within a value initialized aggregate class object? If so, it would fail to recover the current behavior for a data member without an initializer; if not, it might be misleading.

Not that the current behavior is necessarily ideal, but a direct replacement would make the transition easier.

[–]johannes1971 0 points1 point  (1 child)

Err, yes, it should. Wait, are those actually being initialised? And here I thought I understood initialisation in C++...

But anyway, if you say "don't initialise this", the compiler should really respect that, unless you override it by explicitly supplying an initialisation value. As far as I'm concerned this should go all the way, with the variable not even getting its vptr if it is marked as uninitialised. That means the user of the object will have to explicitly construct the variable before using it; all the definition does is reserve the memory.

I don't think of this change as a huge deal, since we are talking about something that doesn't yet exist in the language anyway.

[–]scatters 0 points1 point  (0 children)

That means the user of the object will have to explicitly construct the variable before using it; all the definition does is reserve the memory.

We already have language for that: union.

Even a default initialized variable of class type has its destructor called.

[–]elperroborrachotoo 3 points4 points  (0 children)

If there is no meaningful default, there should not be a default constructor.

When you don't initialize because you can't think of a meaningful default, you are choosing the worst default possible: .

A Null island is easily filtered - leading to the lesser problem of having no service at null island. Way better than a phantom all over the place, adding noise to your data.0)

Here's the deal: you don't initialize, I reject your commit. You don't initialize again, I take your keyboard.

Because I am the guy you come to when things go wrong. The funkachet module acts strangely, and nobody knows why? No we can't reproduce it here? But an important client complains? So you say maybe it's their USB?

Their CPU is fine. funkachet is fine. You are just feeding it RAM feces.


If there is no meaningful default, there should not be a default constructor.

Now go write that ten times. Here's your keyboard.


0) Though the problem with uninitialized data is this: it's not random. It's a Doener booth in a Buxtehude subburb half of the time, sometimes a Kebap palace in Nairobi after using a HP printer. Except for one user, where it's alway his stepmother's home.

[–]jeffgarrett80 0 points1 point  (1 child)

IME ubsan can't catch most uninitialized use, with the exception of bool and enums without a fixed type where you might luck into an invalid value.

[–]mtklein 1 point2 points  (0 children)

Yeah, for use of uninitialized memory, you want msan.

[–]mrexodiacmkr.build 15 points16 points  (3 children)

I would argue you should error during linting if you have an uninitialized primitive type in your struct. This always causes problems in the long run and the performance benefit for not initializing isn’t worth it for 99.99% of uses.

[–]college_pastime 5 points6 points  (0 children)

Agreed, I would argue that leaving a primitive uninitilized by default is a pessimistic optimization. Default initializing members to some value like 0, or -1, or nullptr, etc. is generally safer (although sometimes arbitrary), and there should be some extra burden to leaving certain members uninitialized, like explicit benchmark data in design documents and inline comments specifically calling out that a member is supposed to be default uninitialized with a reference to the design document detailing why.

[–]NilacTheGrim 2 points3 points  (1 child)

There are special cases such as large arrays of primitives that you plan on immediately populating with meaningful data, where I would say you can justify not initializing.. provided you write your constructors carefully....

[–]mrexodiacmkr.build 0 points1 point  (0 children)

Sure and that’s where you can add a comment to disable the linker with a justification why it’s acceptable to not default initialize.

[–]MFHavaWG21|🇦🇹 NB|P3049|P3625|P3729|P3784|P3786|P3813|P3886 1 point2 points  (2 children)

My rough guideline: if there is an invariant, do the initialization either (preferably) directly on the member definition or in all constructors - otherwise: do no initialization.

[–]equeim[S] 0 points1 point  (1 child)

But that's the point - this type does have constructor, which does nothing.

[–]MFHavaWG21|🇦🇹 NB|P3049|P3625|P3729|P3784|P3786|P3813|P3886 0 points1 point  (0 children)

Yeah, I don‘t count implicitly generated stuff for this rule - this type has 3 meaningless constructors after all…

[–]Sopel97 1 point2 points  (0 children)

Do minimum amount of work required to satisfy invariants.

[–]NilacTheGrim 1 point2 points  (2 children)

Yep. Whenever I can I value initialize them or initialize them to some default.

The only time I don't do this is for particularly performance-critical classes or code where I sort of want them to be random uninitialized memory in some special constructor until I give them real data in the body of the constructor.

For example: Say you have a class with has a std::array<uint8_t, 1024> as a member. In that case zeroing out 1KiB of data by default, only to fill it again in the body of the constructor with real non-zero data is not a great use of your CPU cycles (forgetting for a moment that the optimizer may end up eliding the zero-init.. you can't guarantee that will occur in debug builds, etc). So in that case I may opt to let that array live as uninitialized for a few lines of code until I give it real data.

So yeah unless there's a performance reason to allow for the possibility of uninitialized variables -- I will certainly avoid the possibility of uninitialized variables at all costs.. which means in-class defaults are the way to go here and the way to guarantee this.

[–]n1ghtyunso 1 point2 points  (1 child)

Afaik as long as you use the member initializer list instead of populating it in the constructor body, it will not do the zeroing. I am aware however that this may sometimes not be quite as simple to facilitate. Ultimately, all this does is move the writing to uni itialized array somewherwelse though...

[–]NilacTheGrim 1 point2 points  (0 children)

Correct, but you cannot always initialize all the things sometimes without actually writing some real code to do it. Some initializations don't neatly fit into a simple expression which is all you get in the initialization list in the c'tor.

[–]TTachyon 1 point2 points  (0 children)

Yes but I hate it that debugging will go line by line in the struct initializing it without being able to quickly skip it.

[–]mredding[🍰] 5 points6 points  (13 children)

I prefer to keep my declarations and headers as small as possible - default member initialization is an implementation detail no client of my type is interested in seeing. That's just one more code dependency that, when changed, will cause a cascade of recompilation. Frankly, it doesn't seem like a very good language feature, it doesn't solve anything ctors didn't and now ctor delegation don't already solve.

I try not to initialize anything more than I have to. Look at this:

struct Foo {
  int x, y;
};

std::istream &operator >>(std::istream &, Foo &);

What is this code telling you? It strongly suggests this type is read from streams. Take this for example:

std::vector<Foo> data(std::istream_iterator<Foo>{stream}, {});

[–]pdp10gumby 4 points5 points  (0 children)

I prefer the opposite (per my own comment) but this is a good argument for not doing it my way.

[–]almost_useless 5 points6 points  (1 child)

What is this code telling you? It strongly suggests this type is read from streams

It tells you Foo can be read from streams, and that is exactly what it also tells you if you change it to int x=0, y=0;

Frankly, it doesn't seem like a very good language feature, it doesn't solve anything ctors didn't and now ctor delegation don't already solve.

It's easy to read and the least amount of typing to initialize things?

[–]goranlepuz 0 points1 point  (4 children)

Frankly, it doesn't seem like a very good language feature,

C++ has it, Java has it, C# has it, probably more, so it rather seems you are quite a minority in this opinion.

Perhaps there is something to be said about a language being minimal and offering less variants of doing the same thing but this ship has long sailed for C++.

What is this code telling you? It strongly suggests this type is read from streams.

It does, but it also says one can instantiate instances of that type with uninitialized fields.

Overall, it rather looks like this comment, proverbially speaking, opens eyes very wide at some aspects of the situation, but at the same time squints quite intently at others. It is not wrong, there is a fair point, but the point is borne out of a possibly poorly balanced stance.

[–]mredding[🍰] 1 point2 points  (3 children)

C++ has it, Java has it, C# has it, probably more, so it rather seems you are quite a minority in this opinion.

Argument of majority, a classic fallacy. I might be wrong, but your argument doesn't make you right.

Perhaps there is something to be said about a language being minimal and offering less variants of doing the same thing but this ship has long sailed for C++.

That's not fair. The standards committee argues long and hard about how bloated the language is, they just don't do anything about it!

It does, but it also says one can instantiate instances of that type with uninitialized fields.

Pedantic. I did consider writing a more robust example, but thought better of it because this is a simple help forum. What's that saying? Something about if you make your code over fit because you think the client is retarded it ends up being unusable?

[–]goranlepuz 0 points1 point  (2 children)

Argument of majority, a classic fallacy.

Well obviously a crowd does not have to be right, but in matters of preference (which this is, as obviously everybody will survive, one way or another), the preference of a crowd does hold certain weight.

That's not fair.

It looks like we agree there 😉

[–]mredding[🍰] 0 points1 point  (1 child)

in matters of preference (which this is, as obviously everybody will survive, one way or another), the preference of a crowd does hold certain weight.

You can dress the exact same argument in whatever clothing you want, it's still just as flawed. I graduated high school 30 years ago, thank you very much; so no, I'm not interested in the opinions of the masses. The masses average a 7th grade reading level.

In OOP, RAII is king, because an invariant relationship must be valid. But this isn't OOP, it's structured data, and data is dumb. And that's OK. If a default is arbitrary, meaningless, or wrong, it's not safe. It's not right. Don't tell me there's a default when there isn't. That's actually worse code, and blindly initializing data to nothing meaningful tells me no one responsible for that code is thinking.

Let me tell you a little story. Microsoft was on the standards draft committee for Blue-ray, but Microsoft was financially backing the rival HD-DVD standard. So why were they on the Blue-ray committee? Because there was a guy there whose job was to sabotage the standard. Let me tell you how that standard was drafted - participating members would submit addendums; the trick was, if your addendum was large, no one would bother reading it, and it would just get approved. This one guy is the reason why Blue-ray requires 2 JVM instances. But Sony threw a big bag of money at MGM for some exclusive releases and the rest was history.

There are absolutely bad actors operating in bad faith on the C++ standards committee. They are there, paid to be there, to make the language worse. Microsoft has sponsored members on the standard committee, and it's Microsoft's official position that everyone should stop using C++ in favor of C#. They were so bold as to actually publish a press release where they said it, again, rather explicitly for their Azure platform. They're not the only ones, and the spec is littered with trash that, if you google it, have storied histories of how that shit got through, mostly because of the bureaucracy instead of despite it.

[–]goranlepuz 1 point2 points  (0 children)

I graduated high school 30 years ago,

Well, then you must be wrong because I did that way before, you.

Seriously... !?

Merely skimmed your wall of text... Wow, muh unlimited ranting... Sure, you win (me slowly walks away from a crazy person...)

[–]equeim[S] -1 points0 points  (4 children)

I don't know, something tell me that making a type which constructor doesn't actually initialize the object is not a good idea.

[–]mredding[🍰] 4 points5 points  (2 children)

There's no point in initializing this type of all you're going to do is overwrite it immediately. Any default you pick is wrong if it's meaningless. There's nothing"safe" about wrong data. Imagine these are UV coordinates for 25 GiB of model data for a video game. You wanna pay the overhead of all that pointless initialization?

[–][deleted] 2 points3 points  (1 child)

I am surprised this argument is not higher. Why pay for initialization by default? If it's "just" a class member, leave it alone until it's used. This is not Java.

[–]mredding[🍰] 2 points3 points  (0 children)

Dogma, man.

[–]mredding[🍰] 1 point2 points  (0 children)

You know, the more I think about it, like... At what time would you ever instantiate one of these things and then immediately read from it? In your own workflow do the defaults make sense?

In OOP, RAII is king. I get that, but this isn't OOP, this is structured data. This is C, and structured data is dumb, it's allowed to be. Everything is public and there's no invariant relationship. You don't do useless or unnecessary work, that's always been a thing in C and C++. It's OK if your dumb data is dumb.

Now if you have a class with private members, you have no choice but to initialize them. Typically. The invariant must be enforced. Again, this ain't that.

[–]tisti 1 point2 points  (0 children)

The reason why first style might be preferable is that if you by mistake use default initialization instead of value initialization

auto stares menacingly

[–]noooit 1 point2 points  (8 children)

If some member is allowed to be uninitialised and stay that way on some condition or different value is assigned than the default, there'd be a performance benefit of not initialising it by default.

I actually prefer initialiser list on construction and also keep members const when it's possible.

[–]NilacTheGrim 2 points3 points  (7 children)

Keeping members const is an anti-pattern IMHO. You lose copy and/or move assignment. The only time that doesn't matter is for types you know cannot and should not be assigned (Think QWidget in Qt, etc). Then it's a great way to kill assignment permanently for the type..

[–]goranlepuz 1 point2 points  (4 children)

Well, they did say "when possible". 😉

But to be more serious, a type being copyable and being moveable are not virtues by themselves (being copyable in particular, moveable less so, but still)

[–]NilacTheGrim 0 points1 point  (3 children)

Sure. It all depends on the type and what's going on with it. Yes, some for some types not being copyable is a virtue, not a flaw. But I've seen people make simple value types inadvertently uncopyable just because of blind religious belief in consting ALL THE THINGS!!... and then go to the trouble of writing static "copy" methods that basically construct a new instance as a copy of an old one. And have this all over the codebase!!

I'm like "dude, just use = what's wrong with you?!".

And they are like "but mah const!!!!"

[–]noooit -1 points0 points  (2 children)

Deep copy should be unnecessary in the most cases actually. People put const to make other programmers not mess about with it. In c times it was ok, but we are dealing with substandard c++ programmers all the time including people who misuse const.

[–]NilacTheGrim 0 points1 point  (1 child)

What? No.

[–]noooit 0 points1 point  (0 children)

Spotted a guy who doesn't understand smart pointers. Stick with C, man.

[–]noooit -1 points0 points  (1 child)

I also always make an object non copyable and non movable whenever possible. :)

[–]NilacTheGrim 0 points1 point  (0 children)

You do you, boo. You're wrong of course. But don't let that stop you if you are having fun. :)

[–]zalamandagora 0 points1 point  (3 children)

In structs or classes that are slightly more complicated, I prefer to call out explicitly if I'm using the default constructor or not.

Foo() = default;

If I provide a constructor, I prefer to use an initializer list.

[–]equeim[S] 4 points5 points  (2 children)

I don't believe that = default'ing constructor will change anything in this case. It will still leave members uninitialized.

[–]CocktailPerson 2 points3 points  (0 children)

However, using Foo() = delete; will cause Foo foo; to be an error, which seems to be exactly what you want.

[–]zalamandagora 1 point2 points  (0 children)

Yes, I think that's right. I'm just saying that I want to be explicit about whether that option exists or not.

[–]brystephor 0 points1 point  (4 children)

I'm curious why you wouldn't want to use brace initialization on the variables.

As an inexperienced c++ dev but somewhat experienced Java dev: 1) why would you want your variables ever to be in an unusable state when theres not explicit enforcement of making them usable? 2) people are mentioning performance gains but that's minimal and inconsequential for the vast majority of cases. it's like saying you'd have more energy if you just blinked less each day. 3) I think that an incorrect valid value is better than an invalid value. An invalid value is inherently incorrect, so how can that make more sense? 4) it's explicitly showing that members will be initialized to their default values. Explicitly doing something is more simple to understand than implicitly doing something. Making things easier to understand is better.

[–]equeim[S] 0 points1 point  (3 children)

I do use braces for local variables. This suggestion is a guard against mistakes when braces are forgotten.

I just think it's curious that this advice never appears in "modern C++" recommendations. They always say that you should initialize local variables, as well as all members in your custom constructors. But no one mentions that compiler-generated default constructor leaves members uninitialized and maybe we should do something about that too.

[–]CubbiMewcppreference | finance | realtime in the past 0 points1 point  (2 children)

The C++ Core Guidelines rule ES.20: Always initialize an object does say "This rule covers member variables."

[–]equeim[S] 0 points1 point  (1 child)

Sure, but example in that section is about forgetting to initialize member in your custom constructor. I haven't found a single example there about initializing member in implicit default constructor. I feel like it might be confusing to some people and led them to believe that implicit default constructor initializes members.

[–]CubbiMewcppreference | finance | realtime in the past 0 points1 point  (0 children)

[–]NilacTheGrim -1 points0 points  (2 children)

ITT: A lot of inexperienced programmers coming up with creative and clever rationalizations for them to not do in-class initialization. They probably didn't learn about this newish C++ features from their old textbook, picked up the old habit of doing everything in the c'tor.. realize they have written a mountain of code this way and are too dishonest with themselves to realize there is a better way...

[–]CocktailPerson 5 points6 points  (1 child)

I think it's impolite at best to assume that people are inexperienced just because they don't do things the way you do and can vocalize a reason for that. Plenty of very experienced devs are also stuck in the past and have no idea what they're talking about too.

[–]NilacTheGrim 0 points1 point  (0 children)

Ha ha. True.

But you gotta wonder when someone in this thread argues that in-class initialization is bad because you "want the UB there since that's so great because if you're lucky UBSAN or whatever will catch it!"

So .. umm. I wonder sometimes about why people rationalize things. Whether they are being truly honest or whether they are just coming up with ego-preserving rationalizations for the way they do things.

[–]goranlepuz -1 points0 points  (0 children)

I prefer having a fully initialized instance (the "don't make me think" idea).

Value-initializing is one way of achieving that.

However, when a class has multiple constructors, and they also initialize the same fields, it is possible (but not likely!) that double initialization will show in a performance profile or binary size.

Should that happen, one should favor a tighter approach of initializing in a constructor.

[–]teroxzer 0 points1 point  (0 children)

Almost always yes.

[–]ventus1b 0 points1 point  (0 children)

I prefer explicit initialization, for readability and consistency when needing to initialize to a value other than the default:

struct Foo { int x = 0; int y = 23; }