Two Weeks of C++ - Day 01

Intro

This is day 01 of the Two weeks of C++ series. Today is about the basics of programming and C++ notation, which is basically C. I’ll point out some good advice by the author and the new stuffs I learned from this chapter.

If it’s your first time here, please, read the disclaimers before moving on.

The basics

Functions

The number of errors in code correlates strongly with the amount of code and the complexity of
the code.

~ Bjarne Stroupstrup

The author recommends to use functions as much as possible to encapsulate repeating code blocks. This helps with more maintainable and shorter code.

I’m not including function overloading because I already know this from some other OOP languages.

The curly-brace-delimited initializer list

On top of the traditional = sign that we use to initialize a variable, C++ offers a {} delimited way of doing it too:

1
2
double d1 = 21;
double d2 {21};

It doesn’t feel natural at first, but you get used to it. It’s not only the syntax, it’s a good way, some times, to make sure your variable value doesn’t get converted to the variable type (implicit conversion):

1
2
3
int i1 = 42.3; // Becomes 42 (implicit conversion)
int i2 {42.3}; // Error: floating-point to integer conversion
int i3 = {42.3}; // Same error, you can use both ops but it's not necessary

Types and Variable declaration/initialization

Variables

A variable should only be left uninitialized in extremely rare circumstances. Don’t introduce a name until you have a suitable value for it.

~ Bjarne Stroupstrup

Here, the author is really keen about not declaring empty variables and I gotta say, I don’t quite like it.

At 42, they ask us to declare every variable we want to use at the top of the function. I think it really helps to know all the variables that are going to be used later so that you can already guess the logic behind the function.
I know it adds extra lines and we can explain the logic in a comment, but it seems so much cleaner that way :/

I think it’s going to be a great advice for competitive programming, but I’ll keep the 42 style for software engineering in low level languages.

It’s also good to know that user-defined types (string , vector , Matrix , Motor_controller , and Orc_warrior) can be defined to be implicitly initialized. So no need to initialize them.

The auto type

In generic programming it can be hard to know what the type of an object will be and the type name can be long. That’s when auto comes into play:

1
2
3
4
5
auto torf = false; // a bool
auto c = 'i'; // a char
auto i = 42; // an integer
auto d = 21.12; // a double
auto whatever = sqrt(42); // Whatever type `sqrt` returns

It’s best to use auto when:

  • The definition is in a small scope and we don’t need to make the type clearly visible to readers of the code
  • we don’t need to be explicit about a variable’s type or precision (float vs double)

Constant expressions

We already know the const specifier. C++11 introduced constexpr. So the main difference between those two is that constexpr is mainly for optimization purposes, whereas, const is to assure a variable won’t be changed later in the program. Although, They are both used for better code maintainability.

constexpr makes sure to compute expressions at compile time so that time can be saved when code is ran. Faster and more maintainable code as a result.

Here’s a good example, courtesy of Geeksforgeeks:

Fibonacci examplefib.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;
constexpr long int fib(int n)
{
return (n <= 1)? n : fib(n-1) + fib(n-2);
}
int main ()
{
const long int res = fib(42);
cout << res;
return 0;
}

So this is a program that computes the Fibonacci number of the number 42. If I compile then run this on my machine using g++ -std=c++11 fib.cpp; time ./a.out, it only takes at most 0.003s.

When I modify the line 12 into long int res = fib(42);, it takes 3.169s! Crazy, right? That’s because the result of the constexpr is not used in a constant expression, therefore it’s evaluated every time you run the program, not at compile time.

The range-for-statement

The range-for-statement is a simpler way to traverse a sequence in C++. Instead of the usual

1
2
3
4
int nums[] = {1,2,3};
for (auto i = 0; i < 3; ++i)
std::cout << nums[i] << '\n';

We can do

1
2
3
4
int nums[] = {1,2,3};
for (auto i : nums)
std::cout << i << '\n';

This reminds me of the for...of statement of JavaScript (I love JS by the way). But it’s even better than for...of, it lets us get each element by reference so we can modify the array on the go, awesome:

1
2
3
4
5
6
int nums[] = {1,2,3};
for (auto &i : nums)
++i;
// at this point, `nums` is {2,3,4}

The unary & operator

Coming from a C background, we all know the & op means pass by reference when passing arguments to a function. Well, in C++, it does even more.

For example, in a function definition we can accept an argument by reference so the compiler doesn’t need to copy the variable for us to use it (performance!):

1
void sort(vector<double>& v); // sort v

On top of that, if we don’t want to modify the variable but still don’t want the cost of copying it, we can add the const specifier:

1
double sum(const vector<double>&)

This is a very common thing to do in C++. One more thing to notice is that we don’t have to dereference the variable using the unary * op. I’ll talk a little bit more about this in the next post.

The nullptr

In C, we’re used to test variables against 0 or NULL (stdlib.h). In C++, nullptr is used for pointers. It’s a good way for the reader of the code to differentiate integers from pointers

Conclusion

Phew! That was a great first day. The rest is even better! Yes, I live in the future XD.

The author leaves some great advice at the end of each chapter, they are mainly related to what he talked about during the whole chapter, so I’ll only point out the ones I think are the most important:

  • Focus on programming techniques, not on language features. Remember, C++ is just a tool among a lot of other tools!
  • Package meaningful operations as carefully named functions.
  • Use overloading when functions perform conceptually the same task on different types
  • If a function may have to be evaluated at compile time, declare it constexpr
  • Declare one and only one name per declaration
  • Prefer the {} initializer syntax for declarations with a named type

The other advice are great, so I recommend you get the book ;)