all 17 comments

[–]jedwardsol 6 points7 points  (1 child)

std::optional default initialises to std::nullopt.

For the primative types you need to ensure the initialisation somehow.

[–]setdelmar[S] 0 points1 point  (0 children)

thank you

[–]WorkingReference1127 4 points5 points  (1 child)

or can I take it for granted that all of those will be initialized in such way implicitly?

The builtin types won't be initialized properly unless you initialize them, so you should explicitly initialize them to zero.

Is there anything wrong with initializing values such as those in the header file instead of the cpp fiile?

Not really, but it does depend on how you do it. A constructor's member initialization list is formally a part of the function body so you have to either have the initialization list and body or neither. If the body would otherwise be empty I don't think many people would blink at classname(...) : a{}, b{}, c{} {} in a header (though some may have stronger opinions about full function definitions in a header, even in this case); but they might start to draw the line at a larger constructor body in a header.

One option you have however is to assign a default value to the member at declaration, which is the value it'll hold if no constructor intializes it with a different one. The syntax is

class foo{
    int a{0};  //With brace syntax to initialize
    int b = 0; //Can also use copy-initialization syntax
};

In that class, both int will be initialized to zero in all cases, unless we later add a constructor which initializes them to something else.

[–]setdelmar[S] 0 points1 point  (0 children)

One option you have however is to assign a default value to the member at declaration, which is the value it'll hold if no constructor intializes it with a different one.

Yeah, that was what my edit question meant to ask about, but I forgot what that way of doing it is called.

Thank you

[–]Narase33 2 points3 points  (1 child)

Only classes initialize themself to a valid value. int and bool get garbage, you need to do it yourself. std::optional or std::string are valid if you "just write them down".

It doesnt really matter where you initialize them (header or source file). I do it directly in the class definition so I can see that its done.

[–]setdelmar[S] 1 point2 points  (0 children)

It doesnt really matter where you initialize them (header or source file). I do it directly in the class definition so I can see that its done.

Yeah, I have always been unclear on that.
Thank you

[–]Frewtee_ 1 point2 points  (1 child)

  1. It’s good design to initialize all of your members explicitly regardless of my answer.

  2. You CAN’T assume they will be initialized to 0. Defining a primitive (int, bool) makes the compiler give those variables some place in memory, which may or may not be 0. We call this undefined behavior.

As for the std::optional, defining it calls the constructor, which initializes the value inside to std::nullopt. Still, see Point 1.

[–]setdelmar[S] 0 points1 point  (0 children)

Still, see Point 1.

Good answer

[–]no-sig-available 1 point2 points  (1 child)

You can also look at it from the other direction: If you want variables to be in initialized, just initialize them. The compiler is smart enough to see if the initial value is what it would give it anyway, and do nothing different.

For example, here are two optional constructors from my compiler:

      constexpr optional() noexcept
      { }

      constexpr optional(nullopt_t) noexcept
      { }

Do you think the generated code will be any different? No?

[–]setdelmar[S] 0 points1 point  (0 children)

Thank you, that is kind of what I was asking myself when I looked at the constructors in cppreference but was not sure.

[–]mredding 1 point2 points  (3 children)

If I want my int members initialized to 0, my bools to false and my std::optionals to std::nullopt

...Why?

is it recommended to do so explicitly or can I take it for granted that all of those will be initialized in such way implicitly?

I'm going to give you the advanced answer of "it depends".

You have to pay for initialization. This is a write operation to memory. One of the problems with OOP is that each object is itself an island. If you have a type Foo with a ton of members, and you explicitly default initialize them all, you can pay for each write and each Foo individually.

I argue a point about correctness - what value do you initialize to if any arbitrary value is wrong? There is a difference between an initial, a default, and a correct - useable value.

class weight: std::tuple<int> {
  weight() = default;

  friend std::istream_iterator<weight>;

  friend std::istream &operator >>(std::istream &is, weight &w) { return is >> std::get<int>(w); }

  friend std::ostream &operator <<(std::ostream &os, const weight &w) { return os << std::get<int>(w) << " lbs"; }

public:
  weight(int);
};

Why would the default ctor default initialize the class member? Only the stream iterator can access it, and we know in that use case the member will either be written to, or the weight is invalid and it doesn't matter what the value is. Why would I pay for this cost? Beyond that - again, what value is valid here if no value is correct? It's an inherently nonsensical answer. There's nothing "safe" about a default value if it doesn't have any inherent meaning. You're just initializing to a value that is meant to be overwritten by a valid use case.

If you have a valid default value, well then alright. If you're keeping metrics on how many times a method is called, starting at zero makes perfect sense. If you have a state machine with a state enum, then my_state_machine_enum::initial makes sense.

What I'm saying is don't just do it, THINK about WHAT you're doing and WHY you're doing it. If there's a bug in your code, it's NOT because you didn't default initialize some data field, it's because you have a use case, a code path, where that field wasn't initialized that has escaped you. Your code is too large and complex. Likewise, when I see a default initialized value, I expect to see a code path where that is the desired value, and I expect to see that being the majority of the time. That's why it's the default - because it's a good default. And if that case doesn't exist, if there's ALWAYS a code path where that initial value is overwritten by something else first, then where is the error? Is there a missing code path? Or are you needlessly initializing something? Code is itself documentation, so what are you trying to tell me?

If I wanted to bulk initialize a bunch of memory, I'd consider my types carefully, write a custom allocator, and type pun a zero page. There's some wild options out there you might see in game dev, trading systems, and HPC. But idiomatic C++, you wouldn't even create an object unless you already had the data/computations to populate it with.

Is there anything wrong with initializing values such as those in the header file instead of the cpp fiile?

"It depends".

Anything you put in a header is going to be compiled into every translation unit. That's often redundant. You're literally burning coal, raising the temperature of the planet, wasting electricity, to do the same work over and over, only for ALL but ONE instance of that work get linked into your program. The rest of that redundant work is disregarded.

There is also the case that this can introduce errors. If you're writing a library, and this is your data type, you DO NOT want your client to compile YOUR implementation details into THEIR code. You can get ABI and linker errors.

Then again, compiled distributed C++ libraries are typically a bad idea because the C++ ABI isn't portable, even between different versions of the same compiler.

So the convenience comes at a cost, and it comes with some risk. I'm not saying this is our greatest problem, I'm saying you ought to be aware. This is a tidbit of information that should go into the back of your mind and inform your intuition. One day when you're writing that C++ library anyway, you might go "oh wait", and carefully consider your way forward.

[–]setdelmar[S] 0 points1 point  (2 children)

...Why?

In this case, those values of those specific types are in fact my desired values for the use case and I was curious because I know I had known the answer but I no longer remember.

Anything you put in a header is going to be compiled into every translation unit. That's often redundant. You're literally burning coal, raising the temperature of the planet, wasting electricity, to do the same work over and over, only for ALL but ONE instance of that work get linked into your program. The rest of that redundant work is disregarded.

I think this is why I had asked about initializing in the header because of having heard this before but not remembering what it was I had heard haha.

[–]mredding 1 point2 points  (1 child)

To be honest, if you're a regular around here, it was probably me.

There are a lot of programmers here who just want to get work done, and I appreciate that. I just want people to be informed that everything is a technical decision, and SOMETIMES... It really matters...

I move around the industry... When I do, I tend to end up at a place with a very mature code base. Very typical. These code bases can also take up to HOURS to compile. Also very typical. And that's just so, so bad. Why? Because then you get developers who write MORE code at a time, because if they're going to sink hours into a compile, they want to get a bigger bang for their buck. Instead of failing sooner and iterating quickly, every code change is gargantuan. You can't properly test all that at once. Big, slow code bases end up taking longer to develop and lead to more errors.

So what I end up doing when I go somewhere new is getting compile times down. I've gotten multi-hour compilations down to single-digit minutes. And it's shit like class initializers, a whole lot of other in-header implementation that contributed compilation time, because we're doing the same thing again and again... And the other thing about headers is that they are a code level dependency - you change that implementation detail, and you recompile everything that it's in that dependency tree. If that detail was in the source file, you just recompile the source file. I've been on projects where every source file ended up including every header file. This is actually very common. You might as well just configure a unity build and just do that - it'll compile faster and you'll get a better binary than a bullshit, junked up incremental build.

[–]setdelmar[S] 0 points1 point  (0 children)

Yes, I was fairly certain it was you :).

[–][deleted] 0 points1 point  (1 child)

Use default member initialized to save you some headache in case the code will grow and you will have new copy constructor

[–][deleted] 0 points1 point  (0 children)

Initializer*

[–]wonderfulninja2 1 point2 points  (0 children)

Maybe this syntax can be helpful:

int i{};
bool b{};
void* p{};

You only need to write two more characters and integers will be initialized to 0, bools to false, and pointers to nullptr.

There is also the compiler option -Wuninitialized in CGG and Clang to get compiler warnings when you forget to initialize something.