I must say, when I first started dabbling in computer programming, C++ was not at all my first choice of language. The general perception of C++ that I got from my friends and from forums were like…

C++ is a language for really, really smart people.
Each line of code is about 30-40 characters long on average, if not longer.
Everything is needlessly difficult and fraught with pitfalls.
There are so many *, #, &, <>, (), {}, /, [ ], <<, ::, and ! symbols everywhere that it looks like comic book characters swearing.
Before you even think of attempting C++, you should first know C well.
It is impossible to know the whole language. Even people who have been learning it for 15 years have barely even scratched the surface of what is possible.
Template metaprogramming is the devil.

Looking back on these statements today, as someone who now programs mostly in C++ for fun, I see a grain of truth in each of them. C++ is certainly not perfect. Far from it; it is a flawed, verbose, error-prone, needlessly complicated, sometimes rage-inducing language.

But why do I write in it, then, if it’s so terrible? And why for fun? Well, I probably would have never picked up C++ in the first place if the ISO C++11 standard hadn’t come out.

At the time, I was playing around with straight C for a while since I moved to Linux, though with time I found it to be a little too spartan for my taste. I was interested in learning C++ to make games since many well-known game frameworks use the language natively, and I wanted to carry over my C knowledge.

Originally dubbed “C++0x” before delayed to 2011, the C++11 standard added a ton of new and attractive features which attracted people like me, who were previously used to C#, Java, Go, Ruby, Python, etc. Due to extremely positive reception in the C++ community, the ISO committee has been hard at work creating the C++14 standard, and the up-and-coming C++17 standard. These last few iterations have been made such radical changes to the core language that they are colloquially referred to as “modern C++.”

C++11 feels like a new language. I write code differently now than I did in C++98. The C++11 code is shorter, simpler, and usually more efficient than what I used to write.

— Bjarne Stroustrup, creator of C++, https://isocpp.org/tour

These features made me comfortable enough to dive into C++, though they are not necessarily the reasons why I decided to stay. I stayed because I could now work almost as efficiently as I could in C# or Java, but at the same time I had the gory insides of the machine exposed to me. That’s right, I actually found it refreshing to see why the computer behaved the way it did, and not only that, I loved the fact that I could drop the fancy object-oriented stuff and fall back to glorious old C if I ever needed to. C++ also blew my mind several times over in regards to what I thought was possible to get computer programs to do (I’m looking at you, template metaprogramming).

So grab a beverage and some popcorn, because here come my top 5 favorite “modern C++” features.

1. Range-based for loops

This one is probably the most visually striking of them all. It’s probably the first change you notice when examining a modern C++ program.

Say you had a std::vector array of ints and you wanted to loop through them, one by one, and print them to the screen. Compare:

// Classic C++:
for (std::vector<int>::iterator it = array.begin(); it != array.end(); ++it) {
	std::cout << *it << std::endl;
}

// Modern C++:
for (int item : array) {
	std::cout << item << std::endl;
}

Wow! That second one is so much easier on the eyes. This isn’t just for vector arrays; it also works with maps, C-style arrays, lists, stacks, etc.

The best part about this feature is that it uses  the same iterator technology as the former example under the hood. This means that any custom class you create that has an iterator with a begin() and end() function can be used in this type of loop just fine.

Plus, it really looks like like Java’s ranged for loop. Wait, it is Java’s ranged for loop.

2. Braced initializer lists

This change is also a pretty big deal. In the previous feature comparison, I neglected to show you how one would populate the vector array. Here is one possible way in C++03:

// Classic C++:
std::vector<int> array;
for (int i = 0; i < 5; ++i) {
	array.push_back(i);
}

Okay. Kind of messy, but not too hard to understand. But that’s really verbose compared to the modern C++ version:

// Modern C++:
std::vector<int> array = {0, 1, 2, 3, 4};

Pretty cool. You can initialize vectors the same way as you could C-style lists. But modern C++ takes things a stage further. Here’s the modern range-based for loop again from the previous example, made even cooler:

// Modern C++:
for (int item : {0, 1, 2, 3, 4}) {
	std::cout << item << std::endl;
}

Impressive! Rather than build our array first and then iterate through it, it is created on the spot inside the loop with a braced list. Here’s the Ruby equivalent of what we just did:

for item in [0, 1, 2, 3, 4] do
	puts item
end

You can even use braced initializer lists inside methods which accept a struct or a vector:

// Classic C++:
std::vector<std::string> stations;
stations.push_back("CNN");
stations.push_back("TNT");
stations.push_back("Adult Swim");
tv.load_channels(stations);

// Modern C++:
tv.load_channels({"CNN", "TNT", "Adult Swim"});

3. “auto” and automatic type deduction

This new feature is really great for those who love scripting languages! C and C++ are compiled statically-typed languages. This means that you have to compile the code every time you make a change, and you have to explicitly declare what type of data a variable will hold before you use it.

Most scripting languages like Python, Ruby, or Javascript are interpreted dynamically-typed languages, meaning you can run the code immediately after you make a change (or even hot-reload), and you do not have to declare the type of a variable before you use it.

Given its C heritage, it’s doubtful C++ will ever become an interpreted or perhaps JIT-compiled language. But who says it can’t be dynamically-typed? That’s a very nice feature to have, especially given how “chatty” C++ can be with its tedious type names. Apparently, the ISO committee thought so too, since they provided a keyword (or rather, re-purposed an old one) called auto to designate “dynamic type.” Here’s an example usage of the new auto keyword:

// Each pair of lines mean the same thing in modern C++.
std::vector<int> array = {1, 2, 3};
auto array = {1, 2, 3};

std::map<char, std::string> alphabet = {{'a', "apple"}, {'b', "butterfly"}};
auto alphabet = {{'a', "apple"}, {'b', "butterfly"}};

This can save you an incredible amount of typing in a lot of cases! Especially if you are dealing with STL containers like vector, map, list, etc. Also, it’s great to be able to deal with data whose type is determined automatically at compile time. Below is a for loop which can print the contents of any 1-dimensional STL container with an iterator:

std::vector<int> test = {1, 2, 3};
//std::list<int> test = {1, 2, 3};
for (auto &item : test) {
	std::cout << item << std::endl;
}

Using auto in a range-based for loop in general is pretty sweet, since not only do I not have to type out the full name, I also don’t have to change it if I decide to switch containers later.

auto got even cooler in C++14, since now you are permitted to use it in the return type of functions:

// Modern C++:
auto get_all_components() const
{
	std::vector<Component*> buffer;

	for (auto &item : storage_vector) {
		buffer.push_back(item.second);
	}

	return buffer;
}

4. Smart pointers

Pointers are both the saving grace and also the bane of C/C++’s existence, depending on who you talk to. No matter what your opinion on them, pointers are here to stay.

Pointers are powerful but fickle creatures which lurk deep within your machine, hidden from all but the most elite lower-level programming languages, tempting you to join them with seductive talk of “bare-metal access to the machine,” “manual memory management,” and “hand-crafted optimization and power.”

Mesmerized, you follow them into their cave, traveling deeper and deeper down. But it’s all a trap. Cornered and helpless, you watch as they gleefully utter the unspeakable words “Segmentation fault“, horrified as your program crashes horrifically and bursts into writhing flames before your eyes.

Well, not really.

In all seriousness, though, the raw pointer, denoted by an * in code, is a flexible tool which is easy to use but hard to master. A feature inherited from C, pointers in C++ can “point” to specific hexadecimal addresses in memory from which you can instantly look up data that you need without having to painstakingly copy it over yourself. Convenient, to be sure, but they have three major drawbacks which can make them really dangerous at times:

  1. If the data that a pointer is referring to is resized or shifted around in some way, then the pointer’s memory address is invalid. This is called a “dangling pointer.” Attempting to access the pointer is undefined behavior, and may very well crash your program or in rare cases your operating system.
  2. Any data allocated on the heap needs to be explicitly deleted when you’re done with it, or else you will leak memory, and lots of it.
  3. Attempting to delete a pointer twice (a “double-free”) will corrupt your application beyond hope of recovery.

Most modern high-level languages prize safety and simplicity over flexibility and speed. C# tries to discourage pointer use where it can, and Java banned pointers from the language entirely.

C++11 and friends take a very different stance on pointers. They introduce a whole new class of pointer types which are “smart,” in that they grant you the flexibility of raw pointers but in safer, easy-to-use packages:

  • std::shared_ptr: Allows multiple users to look up data. Corrects itself if the data changes. Self-deletes when not used anymore.
  • std::unique_ptr: Allows only one user to to look up data. Corrects itself if the data changes. Self-deletes when not used anymore.
  • std::weak_ptr: Acts just like a raw pointer. No correction if the data changes, but can notify you about it if it does. Self-deletes when not used anymore.

Originally introduced in the Boost C++ libraries and later adopted into C++11, these smart pointer objects avoid virtually all of the common pitfalls with raw pointers. Code written with these pointers tends to be very clean looking, since there is no pointer managing code to be seen, while still retaining the flexibility and great runtime performance that we know and love. On the same token, the software is much more stable and far less likely to crash or puke memory all over the floor than if we had used raw pointers.

5. Lambda functions

If there is one feature that I make extensive use of in my newer programs, it’s definitely lambda functions. Otherwise known as “anonymous functions,” lambdas are basically miniature functions that have no name attached to them. They can be embedded inside other functions, or stored inside of vectors of functions and called back later.

This is how a typical lambda function looks like:

[](int i) { return i + 1; }

Scripting languages, especially Python, Javascript, and Ruby have enjoyed lambdas for years, but most notably the JIT-compiled language C# has also had them for a while (called “delegates”).  Now C++ has them too!

Lambdas are really cool, and they’re great for games. Imagine that you have a 2D space shooter video game. Computer game design principles aside, say that each enemy spaceship is an instance of a class that looks like this:

// Modern C++:
class Enemy : public GameObject {
public:
	Enemy();
	~Enemy();
	void kill();
	void update();

	// "Delegate" is a generic class which stores a stack
	// of std::function objects that can be called at once.
	// In this case, as you see, the return type of this
	// event is "void", but in theory it could be anything
	// you want it to be.
	Delegate<void> event_killed;

private:
	bool is_alive{true};
};

The game loop calls the update() method of the entity on every tick. The event_killed delegate fires immediately once is_alive is set to false.  So, if you wanted an enemy to play a sound effect when it died, you could go to your game logic and do the following:

// Load the enemy_killed sound effect from a file.
Sound sound("resources/audio/enemy_killed.wav");

// Submit a lambda to the delegate which plays the
// sound. The lambda will be executed later when
// the enemy dies.
enemy.event_killed += [&]() { sound.play(); }

What else?

There are so many more excellent features that I love in modern C++ that couldn’t make it into this list: static_assert(), std::function, tuples, constexpr, fast move semantics, the C++14 [[deprecated]] flag, concurrency and multi-threading, and variadic templates.

But with all these changes and additions to the core language and associated standard library, the assertion that no one can ever learn all of C++ in their lifetime becomes ever more realistic. But I don’t mind that; I see C++ no different than a graphic designer would see Photoshop: even the experts still pick up some neat tricks even after 15+ years of experience.

I end with a quote from Richard Feynman:

I can live with doubt, and uncertainty, and not knowing. I think it’s much more interesting to live not knowing than to have answers which might be wrong.

—Richard Feynman, from interview in “The Pleasure of Finding Things Out” (1981)

Postscript: If you find any malformed C++ in my examples or other inaccuracies, please let me know in the comments below. I am still learning, after all. 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s