Improving C++98 Code With C++11

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:

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:

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:

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 typedefs. 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:

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:

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:

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:

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.

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.

2 Comments

Leave a Comment.