0

I came across a code example learncpp.com where they zero-initialized a variable, then defined it with std::cin:

#include <iostream>  // for std::cout and std::cin

int main()
{
    std::cout << "Enter a number: "; // ask user for a number

    int x{ }; // define variable x to hold user input (and zero-initialize it)
    std::cin >> x; // get number from keyboard and store it in variable x

    std::cout << "You entered " << x << '\n';
    return 0;
}

Is there any reason that on line 7 you wouldn't just not initialize x? It seems zero-initializing the variable is a waste of time because it's assigned a new value on the next line.

  • 2
    In general, it is advised that local/block scope built in types should always be initialized. This is to prevent potential uses of uninitialized built in types which have indeterminate value and will lead to undefined behavior. In your particular example though, since `x` gets the value from user(using `cin`) and we are sure that `x` is not used in between the `std::cin >> x` and its definition so we can omit the initialization. – Jason May 20 '22 at 16:16
  • 1
    `it's redefined on the next line` no, there's no redefinition of any variables. And the variable might not be set when you enter an invalid input – phuclv May 20 '22 at 16:16
  • @AnoopRana: Re “This is to prevent potential uses of uninitialized built in types which have indeterminate value and will lead to undefined behavior”: Zero-initializing an object for which zero is not the desired value can also lead to undefined behavior or failure of the program, so does zero-initializing really help? – Eric Postpischil May 20 '22 at 16:20
  • 1
    @phuclv Since C++11, `x` will get set to zero for invalid input. Pretty sure it was added to address not needing to do so yourself. – NathanOliver May 20 '22 at 16:20
  • 4
    Reading input may fail, and with standard before C++11 if that happened `x` would have indeterminate value. Since C++11 `x` will have value `0` on fail, so it's not really required to initialize it explicitly, but I'd personally initialize everything just to keep consistent. – Yksisarvinen May 20 '22 at 16:20
  • @EricPostpischil Can you give some example to support your claim where zero-initializing a built in type in local/block scope results in undefined behavior? – Jason May 20 '22 at 16:22
  • 2
    More undesirable, since C++11, will have a value 0 on fail **or** someone validly entering `0`. The only way to know for sure the value was read successfully from the stream is to check the stream state. Default-value-on-error is a crutch. – WhozCraig May 20 '22 at 16:22
  • 2
    "Not defining" -> "not initializing". `int x;` is still a definition. – HolyBlackCat May 20 '22 at 16:28
  • @AnoopRana: Re “Can you give some example to support your claim where zero-initializing a built in type in local/block scope results in undefined behavior?”: This is obvious. If a program uses `x` to calculate an array index, and all calculations using expected values of `x` lead to indices within bounds but a calculation using zero leads to an index out of bounds, then the behavior of the program will be undefined when it uses the index calculated from a zero-initialized `x` to access an array. There are infinitely many ways wrong data can lead to undefined behavior in a program. – Eric Postpischil May 20 '22 at 16:46
  • @EricPostpischil It is the programmer's responsibility to make sure that logic of the program is correct so that the index won't get out of bounds(especially in C++). Note that i said at the beginnig of my comment *"in general"*. There will always be use cases where you(we) have to be extra careful. – Jason May 20 '22 at 16:55
  • @AnoopRana: Sure, and it is also the programmer’s responsibility to make sure that the logic of the program is correct so that uninitialized objects will not be used. So why are you recommending one thing over another? Arguably, leaving objects uninitialized is better because it gives the compiler opportunity to observe the error and report it, whereas, when an object is zero-initialized, the compiler has no way of knowing that is incorrect and reporting it. So you are recommending a coding practice that conceals errors. – Eric Postpischil May 20 '22 at 17:35
  • @EricPostpischil What? Just so that the compiler can report the error to you, you consider that leaving the variable uninitialized is good. You may consider it good i on the other hand certainly not. Especially not for the reason you gave. Anyways, I am not interested in arguing/discussing anything further with you. – Jason May 20 '22 at 17:48
  • @AnoopRana: Yes, having the compiler report the error is good. Because then the programmer knows there is an error and fixes it. When there is an error, as with an incorrectly initialized object, and the programmer does not know about it, then it is not fixed, and a defective program is deployed. That is bad. – Eric Postpischil May 20 '22 at 17:58
  • *"It seems zero-initializing the variable is a waste of time "* YMMV, but if you are on a standard PC, writing a zero takes on the order of 1 nanosecond. Is it worth trying to save that kind of time? – BoP May 20 '22 at 18:54

2 Answers2

3

Is there any reason that on line 7 you wouldn't just not initialize x?

In general, it is advised that local/block scope built in types should always be initialized. This is to prevent potential uses of uninitialized built in types which have indeterminate value and will lead to undefined behavior.

In your particular example though since in the immediate next line we have std::cin >> x; so it is safe to omit the zero initialization in the previous line.

Note also that in your example if reading input failed for some reason then prior to C++11, x still has indeterminate value but from C++11(&onwards) x will no longer has indeterminate value according to the below quoted statement.

From basic_istream's documentation:

If extraction fails (e.g. if a letter was entered where a digit is expected), zero is written to value and failbit is set. For signed integers, if extraction results in the value too large or too small to fit in value, std::numeric_limits<T>::max() or std::numeric_limits<T>::min() (respectively) is written and failbit flag is set. For unsigned integers, if extraction results in the value too large or too small to fit in value, std::numeric_limits<T>::max() is written and failbit flag is set.

It seems zero-initializing the variable is a waste of time because it's redefined on the next line.

As i said, in your example and assuming you're using C++11(& higher), you can safely leave off the zero-initialization.


then defined it with std::cin

No the use of std::cin does not "define" it(x). Definition happened only once when you wrote:

int x{ }; //this is a definition

Even if you leave the zero initialization, then also it will be a definition:

int x;  //this is also a definition but x is uninitialized here
Jason
  • 36,170
  • 5
  • 26
  • 60
1

The problem you're encountering is, that in order to use the >> operator of std::cin, you have to have your variable declared beforehand, which is, well, undesirable. It also leads to your question of whether or not to initialize the variable.

Instead, you could do something like this:

#include <iostream>
#include <iterator>

int main()
{
    std::cout << "Enter a number: ";
    auto const i = *std::istream_iterator<int>(std::cin);
    std::cout << "You entered " << x << '\n';
}

... although that's also problematic, since you're at the end-of-file, then the behavior is undefined.

Now, you might be asking "why is this guys talking to me about alternatives to the common way of doing things, then telling me that the alternative isn't good enough either?"

The point of my doing this is to introduce you to an uncomfortable fact of "C++ life", which is: We are often stuck with design decisions of the standard library which are not optimal. Sometimes, there's really no better alternative; and sometimes there is, but it wasn't adopted - and since C++ does not easily change designs of standard library classes, we may be stuck with some "warts" in the language. See also the discussion in the related question I've opened:

Why do C++ istreams only allow formatted-reading into an existing variable?

einpoklum
  • 118,144
  • 57
  • 340
  • 684