C++ For C# Developers: Part 3 – Variables and Initialization
Today we continue the series by introducing variables and how they’re initialized. This is another basic topic with surprising complexity for C# developers.
Table of Contents
- Part 1: Introduction
- Part 2: Primitive Types and Literals
- Part 3: Variables and Initialization
- Part 4: Functions
- Part 5: Build Model
- Part 6: Control Flow
- Part 7: Pointers, Arrays, and Strings
- Part 8: References
- Part 9: Enumerations
- Part 10: Struct Basics
- Part 11: Struct Functions
- Part 12: Constructors and Destructors
- Part 13: Initialization
- Part 14: Inheritance
- Part 15: Struct and Class Permissions
- Part 16: Struct and Class Wrapup
- Part 17: Namespaces
- Part 18: Exceptions
- Part 19: Dynamic Allocation
- Part 20: Implicit Type Conversion
- Part 21: Casting and RTTI
- Part 22: Lambdas
- Part 23: Compile-Time Programming
- Part 24: Preprocessor
- Part 25: Intro to Templates
- Part 26: Template Parameters
- Part 27: Template Deduction and Specialization
- Part 28: Variadic Templates
- Part 29: Template Constraints
- Part 30: Type Aliases
- Part 31: Deconstructing and Attributes
- Part 32: Thread-Local Storage and Volatile
- Part 33: Alignment, Assembly, and Language Linkage
- Part 34: Fold Expressions and Elaborated Type Specifiers
- Part 35: Modules, The New Build Model
- Part 36: Coroutines
- Part 37: Missing Language Features
- Part 38: C Standard Library
- Part 39: Language Support Library
- Part 40: Utilities Library
- Part 41: System Integration Library
- Part 42: Numbers Library
- Part 43: Threading Library
- Part 44: Strings Library
- Part 45: Array Containers Library
- Part 46: Other Containers Library
- Part 47: Containers Library Wrapup
- Part 48: Algorithms Library
- Part 49: Ranges and Parallel Algorithms
- Part 50: I/O Library
- Part 51: Missing Library Features
- Part 52: Idioms and Best Practices
- Part 53: Conclusion
Declaration
The basic form of a variable declaration should be very familiar to C# developers:
int x;
Just like in C#, we state the type of the variable, the variable’s name, and end with a semicolon. We can also declare multiple variables in one statement:
int x, y, z;
Also like C#, these variables do not yet have a value. Consider trying to read the value of such a variable:
int x; int y = x;
In C#, this would result in a compiler error on the second line. The compiler knows that x
doesn’t have a value, so it can’t be read and assigned to y
. In C++, this is known as “undefined behavior.” When the compiler encounters undefined behavior, it is free to generate arbitrary code for the entire executable. It may or may not produce a warning or error to warn about this, meaning it may silently produce an executable that doesn’t do what the author thinks it should do. It is very important to never invoke undefined behavior and tools have been written to help avoid it.
This undefined behavior does have a purpose: speed. Consider this:
int localPlayerHealth; foreach (Player p in players) { if (p.IsLocal) { localPlayerHealth = p.Health; break; } } Debug.Log(localPlayerHealth);
We know that one player has to be the local player because that’s how our game was designed, so it’s safe to not initialize localPlayerHealth
before the loop. Initializing it to 0
would be wasteful in this case, but the C# compiler doesn’t know about our game design so it can’t prove that we’ll always find the local player and it forces us to initialize.
In C++, we’re free to skip this initialization and assume the risk of undefined behavior if it turns out there really wasn’t a local player in the players
array. Alternatively, we can replicate the C# approach and just initialize the variable to be safe.
Initialization
C++ provides a lot of ways to initialize variables. We’ve already seen one above where a value is copied:
int x = y;
There are also some other ways that aren’t in C#:
int x{}; // x is filled with zeroes, so x == 0 int x{123}; int x(123);
Many more types of initialization exist, but are specific to certain types such as arrays and classes. We’ll cover these later in the series.
All of these initialization strategies can be combined when declaring multiple variables in one statement:
int a, b = 123, c{}, d{456}, e(789);
This results in these values:
Variable | Value |
---|---|
a |
(Unknown) |
b |
123 |
c |
0 |
d |
456 |
e |
789 |
Type Deduction
In C#, we can use var
to avoid needing to specify the type of our variables. Similarly, C++ has the auto
keyword:
auto x = 123; auto x{123}; auto x(123);
Also similar to C#, we can only use auto
when there is an initializer. The following isn’t allowed:
auto x; auto x{};
It’s important to remember that x
is just as strongly-typed as if int
were explicitly specified. All that’s happening here is that the compiler is figuring out what type the variable should be rather than us typing it out manually.
An alternative approach, much less frequently seen, is to use the decltype
operator. This resolves to the type of its parameter:
int x; decltype(x) y = 123; // y is an int
Lastly, since C++17 the register
keyword has been deprecated:
register int x = 123;
It used to request that the variable be placed into a CPU register rather than in RAM, such as on the stack. Compilers have long ignored this request, so it’s best to avoid this keyword now.
Identifiers
The rules for naming C++ identifiers are similar to the rules for C#. They must begin with a letter, underscore, or any non-digit Unicode character. After that, they can contain any Unicode character except some really strange ones.
Additionally, there are some restrictions on the names we can choose:
Restriction | Example | Where |
---|---|---|
All keywords | int for |
All code |
operator then an operator symbol |
operator+ |
All code |
~ then a class name |
~MyClass |
All code |
Any name beginning with double underscores | int __x |
All code except the Standard Library |
Any name beginning with an underscore then a capital letter | int _X |
All code except the Standard Library |
Any name beginning with an underscore | int _x |
All code in the global namespace except the Standard Library |
There is no equivalent to C#’s “verbatim identifiers” (e.g. int @for
) to work around the keyword restriction.
Pointers
Like C#, at least when “unsafe” features are enabled, C++ has pointer types. The syntax is even similar:
int* x; int * x; int *x;
The placement of the *
is flexible, just like in C#. However, declaring multiple variables in one line is different in C++. Consider this declaration:
int* x, y, z;
The type of y
differs between the languages since the *
only attaches to one variable in C++:
Language | Type of x |
Type of y |
Type of z |
---|---|---|---|
C# | int* |
int* |
int* |
C++ | int* |
int |
int |
To make all three variables into pointers in C++, add a *
to each:
int *x, *y, *z;
Or omit a *
so that only some are pointers:
int *x, y, *z; // x and z are int*, y is int
We’ll cover how to actually use pointers more in depth later in the series.
References
C++ has two kinds of references: “lvalue” and “rvalue.” Just like with pointers, these are an annotation on another type:
// lvalue references int& x; int & x; int &x; // rvalue references int&& x; int && x; int &&x;
When declaring more than one variable per statement, the same rule applies here: &
or &&
only attaches to one variable:
int &x, y; // x is an int&, y is an int
Taken all together, this means we can declare several variables per statement and each can have their own modifier on the stated type:
int a, *b, &c, &&d;
The variables get these types:
Variable | Type |
---|---|
a | int |
b | int* |
c | int& |
d | int&& |
We’ll dive into the details of how lvalue references and rvalue references work later in the series. For now, it’s important to know that they are like non-nullable pointers. This means we must initialize them when they are declared. All of the above lines will fail to compile since we didn’t. So let’s correct that:
int x = 123; int& y = x; int&& z = 456;
Here we have y
as an “lvalue reference to an int
.” We initialize it to an lvalue, which is essentially anything with a name. x
has a name and is the right type: int
. The result is that y
now references x
.
z
is an “rvalue reference to an int
.” An rvalue is essentially anything without a name. We initialize it to 456
which has no name but does have the right type: int
. This means that z
now references 456
.
Putting this back together, we end up with multiple variables being declared and initialized when required like this:
int x = 123; int a, *b, &c = x, &&d = 456;
Conclusion
At a high level, variables in C++ are similar to C#. In the details though, there are very important differences. The undefined behavior stemming from not initializing them, pointer and reference characters only applying to one variable in multiple declaration, various new kinds of initialization syntax, and the presence of both lvalue and rvalue references all make for a pretty different landscape even in this basic category of variables.
Later in the series, we’ll expand this topic when we discuss classes, arrays, function pointers, lambdas, and all kinds of other exotic topics. Stay tuned!
#1 by Kyle Ross on June 1st, 2020 ·
Thank you for this!
#2 by Arif Ahmed on February 14th, 2022 ·
Good God. Even in the basics it is so much more complicated.
Thanks for this series btw. Fingers crossed I don’t get scared off and make it further in!
#3 by Jamasb on August 6th, 2023 ·
Thanks for your great document.
Would you please add the difference of () and {} initialization in this section of the article?
Or to mention if they are the same or not?
My suggestion is due to that to me, as a reader, it was not clear what are their differences.
Though it’s so easy to search the web but just sugesting.
Any way Thanks alot
#4 by jackson on August 7th, 2023 ·
I’m glad you’re enjoying the series! Initialization in C++ is a shocking complex topic. Just a few months ago a 294 page book was published on the topic. I tried to stay out of extreme details like that in this series in favor of simplified examples throughout showing what “normal” C++ code looks like. For advanced usage, you’ll need to consult resources like that book, the C++ International Standard, or web searches.
#5 by Jamasb on August 9th, 2023 ·
@jachson thanks for your explanation, I’m pretty new to c++ I’ve been 8 years into c# and I’m shocked that we have a 294 page book only about c++ variables initialization, Gosh c++ seems huge, but also exciting because of guys like you teaching it perfectly, thx again, I’m going to next chapters