There are many projects written in C++98. Moreover, lots of programmers are still used to C++98. However, with the standardization of C++11 and C++14, C++98 is becoming obsolete. In this post, I would like to list some of the features of C++11 that can be used to improve C++98 code in situations where it is possible to use the new standard.
Before we start, let me define the scope of the present post. C++11 introduced many new features and it is virtually impossible to cover them all in a single post. To this end, I do not provide much details on the changes nor is my goal to cover them all. Rather than that, I list some of the changes I have found useful when transitioning from C++98 and provide links to the details. With that in mind, let’s dive into C++11.
Automatic Type Inference
Starting with C++11, you can use automatic type inference instead of writing an explicit type by yourself. Doing that has many advantages.
// C++98 for (vector<int>::iterator i = v.begin(), e = v.end(); i != e; ++i) { process(*i); } // C++11 for (auto i = v.begin(), e = v.end(); i != e; ++i) { process(*i); }
// C++98 std::map<std::string, int>::iterator pos = m.find(target); // C++11 auto pos = m.find(target);
Further reading:
- Official proposal
- wikipedia.org – C++11: Type inference
- herbsutter.com – GotW #94 Solution: AAA Style (Almost Always Auto)
- akrzemi1.wordpress.com – Gotchas of type inference
- Chapter 2 in Scott Meyers: Effective Modern C++, 2014
Range-Based For Loop
Instead of using iterators to iterate over a collection, you can use a so-called range-based for loop.
// C++98 for (vector<int>::iterator i = v.begin(), e = v.end(); i != e; ++i) { process(*i); } // C++11 for (int& x : v) { process(x); }
You can also use it to iterate over ordinary arrays.
int numbers[] = {1, 2, 3, 4, 5}; // C++98 for (size_t i = 0, e = sizeof(numbers)/sizeof(numbers[0]); i != e; ++i) { numbers[i] *= 2; } // C++11 for (int& x : numbers) { x *= 2; }
Further reading:
Initializer Lists
Instead of creating a container and “initializing” it by adding elements to it afterwards, you can initialize it directly.
// C++98 std::vector<int> v; v.push_back(1); // ... v.push_back(5); // C++11 std::vector<int> v = {1, 2, 3, 4, 5};
This also works when calling a function accepting a container.
void f(std::vector<std::string> v); // C++11 f({"a", "b", "c"});
Further reading:
Lambda Functions
Lambda functions allow you to define functions directly at the place they are to be used.
// C++98 bool cmpById(const Person& p1, const Person& p2) { return p1.getId() < p2.getId(); } // ... std::sort(people.begin(), people.end(), cmpById); // C++11 std::sort(people.begin(), people.end(), [](const Person& p1, const Person& p2) { return p1.getId() < p2.getId(); } );
Further reading:
- Official proposal
- wikipedia.org – C++11: Anonymous functions
- cppreference.com – lambda
- Chapter 6 in Scott Meyers: Effective Modern C++, 2014
Scoped/Strongly Typed Enumerations
By adding the keyword class
after enum
, you create a so-called scoped or strongly typed enumeration. Among the main advantages of them over classical C++98 enumerations belong the next two:
- The values are placed into a new namespace (hence the prefix scoped).
- The values are no longer implicitly convertible to integers or other enumerations (hence the prefix strongly typed).
// C++98 enum MyEnum { VAL1, VAL2, VAL3 }; MyEnum v = VAL1; // OK OtherEnum w = v; // OK int i = VAL2; // OK // C++11 enum class MyEnum { VAL1, VAL2, VAL3 }; MyEnum v = VAL1; // ! (compilation error: missing qualification) MyEnum v = MyEnum::VAL1; // OK OtherEnum w = v; // ! (compilation error: not convertible to other enumeration type) int i = MyEnum::VAL2; // ! (compilation error: not convertible to int)
Further reading:
- Official Proposal
- wikipedia.org – C++: Strongly typed enumerations
- cppreference.com – enum
- nic-gamedev.blogspot.com – C++11: Strongly-typed enum
Variadic Templates
In C++98, it is really cumbersome to have a type-aware function taking a variable number of arguments. If you wanted to support up to X arguments, you had to create X overloads.
// C++98 template <typename T1> void print(const T1& val1) { std::cout << val1 << '\n'; } template <typename T1, typename T2> void print(const T1& val1, const T2& val2) { std::cout << val1; print(val2); } template <typename T1, typename T2, typename T3> void print(const T1& val1, const T2& val2, const T3& val3) { std::cout << val1; print(val2, val3); } // ... print("I am ", 28, " years old."); // I am 28 years old.
In C++11, you can utilize variadic templates and recursion to create a truly variadic function.
// C++11 template <typename T> void print(const T& value) { std::cout << value << '\n'; } template <typename U, typename... T> void print(const U& head, const T&... tail) { std::cout << head; print(tail...); } print("I am ", 28, " years old."); // I am 28 years old.
Further reading:
No Need For an Extra Space In Nested Template Declarations
You can omit the space between two successive >
characters in nested template declarations.
// C++98 std::vector<std::vector<int> > v; // C++11 std::vector<std::vector<int>> v;
Further reading:
Type Aliases
C++11 introduced type aliases, which provide a superior alternative to typedef
s. First of all, any typedef
can be converted into a type alias.
// C++98 typedef void (*fp)(int, int); // C++11 using fp = void (*)(int, int);
More importantly, type aliases allow you to create a typedef template. This cannot be done by using a typedef
.
// C++98/11 template <typename T> typedef std::map<std::string, T> Dictionary; // ! (compilation error) // You wanted to use it in the following way: // Dictionary<int> d; // C++11 template <typename T> using Dictionary = std::map<std::string, T>; // Usage: Dictionary<int> d;
If you wanted such functionality in C++98, you had to use the following, cumbersome code:
// C++98 template<typename T> struct Dictionary { typedef std::map<std::string, T> type; }; // Usage: Dictionary<int>::type d;
Further reading:
- Official proposal
- wikipedia.org – C++: Alias templates
- cppreference.com – Type alias
- Chapter 3, Item 9 from Scott Meyers: Effective Modern C++, 2014
Type-Safe Null Pointer Constant
Instead of using the C macro NULL
or 0
, it is preferred to use the new keyword nullptr
.
// C++98 int *i = 0; // or NULL // C++11 int *i = nullptr;
// C++98 int *func() { return 0; // or NULL } // C++11 int *func() { return nullptr; }
Further reading:
- Official proposal
- wikipedia.org – C++11: Null pointer constant
- wikibooks.org – More C++ Idioms: nullptr
- Chapter 3, Item 8 from Scott Meyers: Effective Modern C++, 2014
Explicit Overrides
C++11 allows you to explicitly specify that you are overriding a virtual member function by using the override
specifier. Its use not only allows the compiler to check that there is a matching virtual member function in the base class, but it also increases code expressiveness.
class Base { virtual void f(int); }; // C++98 class Derived: public Base { virtual void f(double); // Hides Base::f(), does NOT override it. }; // C++11 class Derived: public Base { virtual void f(double) override; // ! (compilation error) };
Further reading:
- Official proposal
- wikipedia.org – C++11: Explicit overrides and final
- cppreference.com – override
- Chapter 3, Item 12 from Scott Meyers: Effective Modern C++, 2014
Explicitly Deleted Functions
In C++98, the usual way of making a class non-copyable was to declare the copy constructor and assignment operator private and do not provide their definition. Since they were private, that could not be called by client code. By not providing their definition, an accidental use of them by the class itself or any friend
classes resulted into a link-time error (undefined function). C++11 provides a more systemic way of doing so – deleting them.
// C++98 class NonCopyable { private: NonCopyable(const NonCopyable&); // no definition NonCopyable& operator=(const NonCopyable&); // no definition }; // C++11 class NonCopyable { public: NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete; }
We can make the functions public because no one is able to call them. More importantly, declaring them public makes error messages more precise if anyone tries to call them.
Further reading:
- Official proposal
- wikipedia.org – C++11: Explicitly deleted functions
- Chapter 3, Item 11 from Scott Meyers: Effective Modern C++, 2014
Non-Inheritable Classes
To make your class inheritable in C++98, you had several options, where neither of them is optimal. Since C++11, you can simply declare the class as final
. This prohibits users from inheriting from it.
// C++11 class NonInheritable final {}; class Derived: public NonInheritable {}; // ! (compilation error)
Further reading:
Non-Overridable Member Functions
In C++98, there is no way of saying that a particular virtual member function cannot be overridden. This is useful when you have a subclass with an overridden virtual member function that you do not want to be overridable anymore.
// C++11 class Base { virtual void f(); }; class Derived { virtual void f() final; }; class MoreDerived : public Derived { virtual void f(); // ! (compilation error) };
Further reading:
Alternative Function Syntax
C++11 introduces an alternative way of writing functions.
// C++98 int f(int x, int y); // C++11 auto f(int x, int y) -> int;
This is useful in templates in conjunction with decltype
(see this example), and to simplify and remove redundancy from member function returning a type from the class:
// C++98 LongClassName::IntVec LongClassName::f() { /* ... */ } // C++11 auto LongClassName::f() -> IntVec { /* ... */ }
Further reading:
Non-Static Data Member Initializers
Instead of repeating the same initializers when having multiple constructors in a class, you can put them directly into the class definition.
// C++98 class A { public: A(int i): i(i), f(1.0), s("some string") {} A(float f): i(0), f(f), s("some string") {} private: int i; float f; std::string s; }; // C++11 class A { public: A(int i): i(i) {} A(float f): f(f) {} private: int i = 0; float f = 1.0; std::string s = "some string"; };
Further reading:
Explicit Conversion Operators
The safe bool idiom can be simplified as C++11 adds support for explicit conversion operators.
// C++98 #include <safe_bool_implementation.h> class ClassWithSafeBool: public safe_bool<ClassWithSafeBool> { // ... bool bool_test() const { // Perform bool logic here. // ... } }; // C++11 class ClassWithSafeBool { // ... explicit operator bool() const { // Perform bool logic here. // ... } }
Further reading:
Standard Library Enhancements
There are many enhancements throughout the entire standard library. I will mention only some of them.
You can use front()
and back()
when referring to the first and last element of a std::string
.
#include <string> std::string str("test"); // C++98 str[0] // first character str[str.size() - 1] // last character // C++11 str.front() // first character str.back() // last character
The standard algorithm
header file contains several new function templates. One of them is std::all_of()
.
#include <ctype.h> #include <string> // C++98 bool hasOnlyDigits(const std::string& str) { for (std::string::const_iterator i = str.begin(), e = str.end(); i != e; ++i) { if (!::isdigit(*i)) { return false; } } return true; } // C++11 #include <algorithm> bool hasOnlyDigits(const std::string& str) { return std::all_of(str.begin(), str.end(), ::isdigit); }
To convert a number into a string, you can use std::to_string()
.
#include <string> // C++98 #include <sstream> std::ostringstream out; out << number; std::string numberAsStr = out.str(); // C++11 std::string numberAsStr = std::to_string(number);
Further reading:
Use std::
Instead of std::tr1::
When using functionality from C++ Technical Report 1 (TR1), you can now use namespace std::
instead of std::tr1::
, and remove the tr1/
prefix from the inclusion of standard headers.
// C++98 #include <tr1/unordered_map> std::tr1::unordered_map<int, std::string> m; // C++11 #include <unordered_map> std::unordered_map<int, std::string> m;
Further reading:
Additional Links
The following links contain material related to the topic of the present post.
- stackoverflow.com – What C++ idioms are deprecated in C++11
- stackoverflow.com – Refactoring with C++ 11
- isocpp.org – C++11: A cheat sheet by Alex Sinyakov
Also, I can highly recommend the new Scott Meyers’ book Scott Meyers: Effective Modern C++, 2014. My review of this gem can be found here.
Finally, if you understand Czech, you can checkout my Czech blog post about new features in C++11.
Worth noting that NSDMI makes an aggregate a non-aggregate, this changed in C++14 because it was such a non-intuitive behavior. This was one of the few negative surprises for me in C++11, see this Stackoverflow question for the details: https://stackoverflow.com/q/27118535/1708801
Yeah, I completely agree. I wrote a blog post dedicated to this issue two years ago because many people got bitten by this non-intuitive behavior.