Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
C++ High Performance
C++ High Performance

C++ High Performance: Boost and optimize the performance of your C++17 code

eBook
€8.99 €29.99
Paperback
€36.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Table of content icon View table of contents Preview book icon Preview Book

C++ High Performance

A Brief Introduction to C++

This chapter will introduce some of the features of C++ that we think are important for writing robust and high performance applications. We will also discuss advantages and disadvantages of C++ over languages based upon a garbage collector. Last, we will look at some examples of how to handle exceptions and resources.

Why C++?

We begin this book by exploring some of the reasons for using C++ today. In short, C++ is a highly portable language which offers zero-cost abstractions. Furthermore, we believe that C++ provides programmers with the ability to write and manage large, expressive, and robust code bases. Let's explore the meaning of each of these properties.

Zero-cost abstractions

Active code bases grow. The more developers working on a code base, the larger the code base becomes. We need abstractions such as functions, classes, data structures, layers and so on in order to manage the complexity of a large-scale code base. But constantly adding abstractions and new levels of indirection comes at a price — efficiency. This is where zero-cost abstractions plays its role. A lot of the abstractions offered by C++ comes at a very low price. At a minimum, C++ offers efficient alternatives at hot spots where performance really is a concern.

With C++ you are free to talk about memory addresses and other computer related low-level terms when needed. However, in a large-scale software project it is desirable to express code in terms that deals with whatever the application is doing, and let the libraries handle the computer related terminology. The source code of a graphics application may deal with pencils, colors, and filters, whereas a game may deal with mascots, castles, and mushrooms. Low-level computer-related terms such as memory addresses can stay hidden in C++ library code where performance is critical.

By library code, we refer to code whose concepts are not strictly related to the application. The line between library code and application code can be blurry though, and libraries are often built upon other libraries. An example could be the container algorithms provided in the Standard Template Library (STL) of C++ or a general-purpose math library.

Programming languages and machine code abstractions

In order to relieve programmers from dealing with computer-related terms, modern programming languages use abstractions so that a list of strings, for example, can be handled and thought of as a list of strings rather than a list of addresses that we may easily lose track of if we make the slightest typo. Not only do the abstractions relieve the programmers from bugs, they also make the code more expressive by using concepts from the domain of the application. In other words, the code is expressed in terms that are closer to a spoken language than if expressed with abstract programming keywords.

C++ and C are nowadays two completely different languages. Still, C++ is highly compatible with C and has inherited a lot of its syntax and idioms from C. To give you some examples of C++ abstractions we will here show how a problem can be solved in both C and C++.

Take a look at the following C/C++ code snippets, which correspond to the question: "How many copies of Hamlet is in the list of books?". We begin with the C version:

// C version
struct string_elem_t { const char* str_; string_elem_t* next_; };
int num_hamlet(string_elem_t* books) {
const char* hamlet = "Hamlet";
int n = 0;
string_elem_t* b;
for (b = books; b != 0; b = b->next_)
if (strcmp(b->str_, hamlet) == 0)
++n;
return n;
}

The equivalent version using C++ would look something like this:

// C++ version
int num_hamlet(const std::list<std::string>& books) {
return std::count(books.begin(), books.end(), "Hamlet");
}

Although the C++ version is still more of a robot language than a human language, a lot of programming lingo is gone. Here are some of the noticeable differences between the preceding two code snippets:

  • The pointers to raw memory addresses are not visible at all
  • The std::list<std::string> container is an abstraction of string_elem_t
  • The std::count() method is an abstraction of both the for loop and the if condition
  • The std::string class is (among other things) an abstraction of char* and strcmp

Basically, both versions of num_hamlet() translate to roughly the same machine code, but the language features of C++ makes it possible to let the libraries hide computer related terminology such as pointers. Many of the modern C++ language features can be seen as abstractions of basic C functionality and, on top of that, basic C++ functionality:

  • C++ classes are abstractions of C-structs and regular functions
  • C++ polymorphism is the abstraction of function pointers

On top of that, some recent C++ features are abstractions of former C++ features:

  • C++ lambda functions are abstractions of C++ classes
  • Templates are abstractions of generating C++ code

Abstractions in other languages

Most programming languages are based on abstractions, which are transformed into machine code to be executed by the CPU. C++ has evolved into a highly expressive language just like many of the other popular programming languages of today. What distinguishes C++ from most other languages is that, while the other languages have implemented these abstractions at the cost of runtime performance, C++ has always strived to implement its abstractions at zero cost at runtime. This doesn't mean that an application written in C++ is by default faster than the equivalent in, say, C#. Rather, it means that by using C++, you'll have explicit control of the emitted machine code instructions and memory footprint if needed.

To be fair, optimal performance is very rarely required today and compromising performance for lower compilation times, garbage collection, or safety, like other languages do, is in many cases more reasonable.

Portability

C++ has been a popular and comprehensive language for a long time. It's highly compatible with C and very little has been deprecated in the language, for better or worse. The history and design of C++ has made it to a highly portable language, and the evolution of modern C++ has ensured that it will stay that way for a long time to come. C++ is a living language and compiler vendors are currently doing a remarkable job to implement new language features rapidly.

Robustness

In addition to performance, expressiveness, and portability, C++ offers a set of language features that gives the programmer the ability to write robust code.

In our experience, robustness does not refer to strength in the programming language itself – it's possible to write robust code in any language. However, strict ownership of resources, const correctness, value semantics, type safety, and deterministic destruction of objects are some of the features offered by C++ that makes it easier to write robust code. That is, the ability to write functions, classes, and libraries that are easy to use and hard to misuse.

C++ of today

To sum it up, C++ of today provides programmers the ability to write an expressive and robust code base while still having the ability to target almost any hardware platform or real-time requirements. Of the most commonly used languages today, C++ is the only one that gives all of these properties.

The aim of this book

This book aims to give the reader a solid foundation to write efficient applications as well as an insight into strategies for implementing the libraries in modern C++. We have tried to take a practical approach to explain how C++ works today where C++14/C++17 features are a natural part of the language, rather than looking at C++ historically.

Expected knowledge of the reader

This book expects you to have a basic knowledge of C++ and computer architecture, and a genuine interest in evolving your skills. Hopefully, by the time you finish this book, you will have gained a few insights into how you can improve your C++ applications, both performance-wise and syntactically. On top of that, we also hope that you will have a few aha moments.

C++ compared with other languages

A multitude of application types, platforms, and programming languages have emerged since C++ was first released. Still, C++ is a widely used language, and its compilers are available for most platforms. The major exception, as of today, is the web platform, where JavaScript and its related technologies are the foundation. However, the web platform is evolving into being able to execute what was previously only possible in desktop applications, and in that context C++ has found its way into web applications using technologies such as Emscripten/asm.js and web assembly.

Competing languages and performance

In order to understand how C++ achieves its performance compared to other programming languages, we'd like to discuss some fundamental differences between C++ and most other modern programming languages.

For simplicity, this section will focus on comparing C++ to Java, although the comparisons for most parts also apply to other programming language based upon a garbage collector, such as C# and JavaScript.

Firstly, Java compile to bytecode, which is then compiled to machine code while the application is executing, whereas C++ directly compiles the source code to machine code. Although bytecode and just-in-time compilers may theoretically be able to achieve the same (or theoretically, even better) performance than precompiled machine code, as of today, they simply do not. To be fair though, they perform well enough for most cases.

Secondly, Java handle dynamic memory in a completely different manner from C++. In Java, memory is automatically deallocated by a garbage collector, whereas a C++ program handles memory deallocations by itself. The garbage collector does prevent memory leaks, but at the cost of performance and predictability.

Thirdly, Java places all its objects in separate heap allocations, whereas C++ allows the programmer to place objects both on the stack and on the heap. In C++ it's also possible to create multiple objects in one single heap allocation. This can be a huge performance gain for two reasons: objects can be created without always allocating dynamic memory, and multiple related objects can be placed adjacent to one another in memory.

Take a look at how memory is allocated in the following example. The C++ function uses the stack for both objects and integers; Java places the objects on the heap:

C++ Java
class Car {
public:
Car(int doors)
: doors_(doors) {}
private:
int doors_{};
};

auto func() {
auto num_doors = 2;
auto car1 = Car{num_doors};
auto car2 = Car{num_doors};
}
class Car {
public Car(int doors) {
doors_ = doors;
}
private int doors_;

static void func() {
int numDoors = 2;
Car car1 = new Car(numDoors);
Car car2 = new Car(numDoors);
}
}



C++ places everything on the stack:

Java places the Car objects on the heap:

Now take a look at the next example and see how an array of Car objects are placed in memory when using C++ and Java respectively:

C++

Java

auto car_list() {
auto n = 7;
auto cars =
std::vector<Car>{};
cars.reserve(n);
for(auto i=0; i<n; ++i){
cars.push_back(Car{});
}
}
void carList() {
int n = 7;
ArrayList<Car> cars =
new ArrayList<Car>();
for(int i=0; i<n; i++) {
cars.addElement(new Car());
}
}



The following image shows how the car objects are laid out in memory in C++:


The following image shows how the car objects are laid out in memory in Java:

The C++ vector contains the actual Car objects placed in one contiguous memory block, whereas the equivalent in Java is a contiguous memory block of references to Car objects. In Java, the objects has been allocated separately, which means that they can be located anywhere in the heap.

This affects the performance as Java has to execute seven allocations instead of one. It also means that whenever the application iterates the list, there is a performance win for C++, since accessing nearby memory locations is faster than accessing several random spots in memory.

Non-performance-related C++ language features

In some discussions about C++ versus other languages, it's concluded that C++ should only be used if performance is a major concern. Otherwise, it's said to just increase the complexity of the code base due to manual memory handling, which may result in memory leaks and hard-to-track bugs.

This may have been true several C++ versions ago, but a modern C++ programmer relies on the provided containers and smart pointer types, which are part of the STL.

We would here like to highlight two powerful features of C++ related to robustness rather than performance, that we think are easily overlooked: value semantics and const correctness.

Value semantics

C++ supports both value semantics and reference semantics. Value semantics lets us pass objects by value instead of just passing references to objects. In C++, value semantics is the default, which means that when you pass an instance of a class or struct, it behaves in the same way as passing an int, float, or any other fundamental type. To use reference semantics, we need to explicitly use references or pointers.

The C++ type system gives us the ability to explicitly state the ownership of an object. Compare the following implementations of a simple class in C++ and Java. We start with the C++ version:

// C++
class Bagel {
public:
Bagel(const std::set<std::string>& ts) : toppings_(ts) {}
private:
std::set<std::string> toppings_;
};

The corresponding implementation in Java could look like this:

// Java
class Bagel {
public Bagel(ArrayList<String> ts) { toppings_ = ts; }
private ArrayList<String> toppings_;
}

In the C++ version, the programmer states that the toppings are completely encapsulated by the Bagel class. Had the programmer intended the topping list to be shared among several bagels, it would have been declared as a pointer of some kind: std::shared_ptr, if the ownership is shared among several bagels, or a std::weak_ptr, if someone else owns the topping list and is supposed to modify it as the program executes.

In Java, objects references each other with shared ownership. Therefore, it's not possible to distinguish whether the topping list is intended to be shared among several bagels or not, or whether it is handled somewhere else or if it is, as in most cases, completely owned by the Bagel class.

Compare the following functions; as every object is shared by default in Java (and most other languages), programmers have to take precautions for subtle bugs such as this:

C++

Java

Note how the bagels do not share toppings:




auto t = std::set<std::string>{};
t.insert("salt");
auto a = Bagel{t};

// 'a' is not affected
// when adding pepper
t.insert("pepper");

// 'a' will have salt
// 'b' will have salt & pepper
auto b = Bagel{t};

// No bagel is affected
t.insert("oregano");


Note how both the bagels subtly share toppings:




TreeSet<String> t = new
TreeSet<String>();
t.add("salt");
Bagel a = new Bagel(t);

// Now 'a' will subtly
// also have pepper
t.add("pepper");

// 'a' and 'b' share the
// toppings in 't'
Bagel b = new Bagel(t);

// Both bagels subtly
// also have "oregano"
toppings.add("oregano");

Const correctness

Another powerful feature of C++, that Java and many other languages lack, is the ability to write const correct code. Const correctness means that each member function signature of a class explicitly tells the caller whether the object will be modified or not; and it will not compile if the caller tries to modify an object declared const.

Here follows an example of how we can use const member functions to prevent unintentional modifications of objects. In the following Person class, the member function age() is declared const and is therefore not allowed to mutate the Person object; whereas set_age() mutates the object and cannot be declared const:

class Person {
public:
auto age() const { return age_; }
auto set_age(int age) { age_ = age; }
private:
int age_{};
};

It's also possible to distinguish between returning mutable and immutable references to members. In the following Team class, the member function leader() const returns an immutable Person; whereas leader() returns a Person object that may be mutated:

class Team {
public:
auto& leader() const { return leader_; }
auto& leader() { return leader_; }
private:
Person leader_{};
};

Now let's see how the compiler can help us find errors when we try to mutate immutable objects. In the following example, the function argument teams is declared const, explicitly showing that this function is not allowed to modify them:

auto nonmutating_func(const std::vector<Team>& teams) {
auto tot_age = int{0};

// Compiles, both leader() and age() are declared const
for (const auto& team: teams)
tot_age += team.leader().age();

// Will not compile, set_age() requires a mutable object
for (auto& team: teams)
team.leader().set_age(20);
}

If we want to write a function which can mutate the teams object we simply remove const. This signals to the caller that this function may mutate the teams:

auto mutating_func(std::vector<Team>& teams) {
auto tot_age = int{0};

// Compiles, const functions can be called on mutable objects
for (const auto& team: teams)
tot_age += team.leader().age();

// Compiles, teams is a mutable variable
for (auto& team: teams)
team.leader().set_age(20);
}

Object ownership and garbage collection in C++

Except in very rare situations, a C++ programmer should leave the memory handling to containers and smart pointers and never have to rely on manual memory handling.

To put it clearly, the garbage collection model in Java could almost be emulated in C++ by using std::shared_ptr for every object. Note that garbage-collecting languages don't use the same algorithm for allocation tracking as std::shared_ptr. The std::shared_ptr is a smart pointer based on a reference-counting algorithm that will leak memory if objects have cyclic dependencies. Garbage-collecting languages have more sophisticated methods that can handle and free cyclic dependent objects.

However, rather than relying on a garbage collector, forcing a strict ownership delicately avoids subtle bugs that may result from sharing objects by default, as in the case of Java.

If a programmer minimize shared ownership in C++, the resulting code is easier to use and harder to abuse, as it can force the user of the class to use it as it is intended.

Avoiding null objects using C++ references

In addition to strict ownership, C++ also has the concept of references, which is different from references in Java. Internally, a reference is a pointer which is not allowed to be null or repointed; therefore no copying is involved when passing it to a function.

As a result, a function signature in C++ can explicitly restrict the programmer from passing a null object as a parameter. In Java the programmer must use documentation or annotations to indicate non-null parameters.

Take a look at these two Java functions for computing the volume of a sphere. The first one throws a runtime exception if a null object is passed to it; whereas the second one silently ignores null objects.

This first implementation in Java throws a runtime exception if passed a null object:

// Java
float getVolume1(Sphere s) {
float cube = Math.pow(s.radius(), 3);
return (Math.PI * 4 / 3) * cube;
}

This second implementation in Java silently handles null objects:

// Java
float getVolume2(Sphere s) {
float rad = a == null ? 0.0f : s.radius();
float cube = Math.pow(rad, 3);
return (Math.PI * 4 / 3) * cube;
}

In both function implemented in Java, the caller of the function has to inspect the implementation of the function in order to determine whether null objects are allowed or not.

In C++, the first function signature explicitly accepts only initialized objects by using references which cannot be null. The second version using pointers as arguments, explicitly shows that null objects are handled.

C++ arguments passed as references indicates that null values are not allowed:

auto get_volume1(const Sphere& s) {   
auto cube = std::pow(s.radius(), 3);
auto pi = 3.14f;
return (pi * 4 / 3) * cube;
}

C++ arguments passed as pointers indicates that null values are being handled:

auto get_volume2(const Sphere* s) {
auto rad = s ? s->radius() : 0.0f;
auto cube = std::pow(rad, 3);
auto pi = 3.14f;
return (pi * 4 / 3) * cube;
}

Being able to use references or values as arguments in C++ instantly informs the C++ programmer how the function is intended to be used. Conversely, in Java, the user must inspect the implementation of the function, as objects are always passed as pointers, and there's a possibility that they could be null.

Drawbacks of C++

Comparing C++ with other programming languages wouldn't be fair without mentioning some of its drawbacks. As mentioned earlier, C++ has more concepts to learn, and is therefore harder to use correctly and to its full potential. However, if a programmer can master C++, the higher complexity turns into an advantage and the code base becomes more robust and performs better.

There are, nonetheless, some shortcomings of C++, which are simply just shortcomings. The most severe of those shortcomings are long compilation times, the reliance on the manual handling of forward declarations, header/source files, and the complexity of importing libraries.

This is mainly a result of C++ relying on an outdated import system where imported headers are simply pasted into whatever includes them. At the time of writing this book, a modern module-based import system is up for standardization, but until the standardized C++ version becomes available, project management remains very tedious.

Another apparent drawback of C++ is the lack of provided libraries. While other languages usually come with all the libraries needed for most applications, such as graphics, user interfaces, networking, threading, resource handling, and so on, C++ provides, more or less, nothing more than the bare minimum of algorithms, threads, and, as of C++17, file system handling. For everything else, programmers have to rely on external libraries.

To summarize, although C++ has a steeper learning curve than most other languages, if used correctly, the robustness of C++ is an advantage compared to many other languages. So, despite the outdated import/library system of C++, we believe that C++ is a well suited language for large-scale projects, even for projects where performance is not the highest priority.

Class interfaces and exceptions

Before diving deeper into the concepts of C++ high performance, we would like to emphasize some concepts that you should not compromise on when writing C++ code.

Strict class interfaces

A fundamental guideline when writing classes, is to relieve the user of the class from dealing with the internal state by exposing a strict interface. In C++, the copy-semantics of a class is part of the interface, and shall therefore also be as strict as necessary.

Classes should either behave as deep-copied or should fail to compile when copied. Copying a class should not have side effects where the resulting copied class can modify the original class. This may sound obvious, but there are many circumstances when, for example, a class requires a heap-allocated object accessed by a pointer member variable of some sort, for example std::shared_ptr, as follows:

class Engine { 
public:
auto set_oil_amount(float v) { oil_ = v; } auto get_oil_amount() const { return oil_; } private: float oil_{};
};
class YamahaEngine : public Engine { //...
};

The programmer of the Boat class has left a rather loose interface without any precautions regarding copy semantics:

class Boat {
public:
Boat(std::shared_ptr<Engine> e, float l) : engine_{e} , length_{l} {}
auto set_length(float l) { length_ = l; }
auto& get_engine() { return engine_; }
private: // Being a derivable class, engine_ has to be heap allocated std::shared_ptr<Engine> engine_; float length_{};
};

Later, another programmer uses the Boat class and expects correct copy behavior:

auto boat0 = Boat{std::make_shared<YamahaEngine>(), 6.7f};
auto boat1 = boat0;
// ... and does not realize that the oil amount applies to both boats
boat1.set_length(8.56f);
boat1.get_engine()->set_oil_amount(3.4f);

This could have been prevented if the Boat class interface were made stricter by preventing copying. Now, the second programmer will have to rethink the design of the algorithm handling boats, but she won't accidentally introduce any subtle bugs:

class Boat { 
private:
Boat(const Boat& b) = delete; // Noncopyable
auto operator=(const Boat& b) -> Boat& = delete; // Noncopyable
public:
Boat(std::shared_ptr<Engine> e, float l) : engine_{e}, length_{l} {}
auto set_length(float l) { length_ = l; }
auto& get_engine() { return engine_; }
private:
float length_{};
std::shared_ptr<Engine> engine_; };

// When the other programmer tries to copy a Boat object...
auto boat0 = Boat{std::make_shared<YamahaEngine>(), 6.7f};
// ...won't compile, the second programmer will have to find
// another solution compliant with the limitations of the Boat
auto boat1 = boat0;

Error handling and resource acquisition

In our experience, exceptions are being used in many different ways in different C++ code bases. (To be fair, this also applies to other languages which supports exceptions.) One reason is that distinct applications can have vastly different requirements when dealing with runtime errors. With some applications, such as a pacemaker or a power plant control system, which may have a severe impact if they crash, we may have to deal with every possible exceptional circumstance, such as running out of memory, and keep the application in a running state. Some applications even completely stay away from using the heap memory as the heap introduces an uncontrollable uncertainty as mechanics of allocating new memory is out of the applications control.

In most applications, though, these circumstances could be considered so exceptional that it's perfectly okay to save the current state and quit gracefully. By exceptional, we mean that they are thrown due to environmental circumstances, such as running out of memory or disk space. Exceptions should not be used as an escape route for buggy code or as some sort of signal system.

Preserving the valid state

Take a look at the following example. If the branches_ = ot.branches_ operation throws an exception due to being out of memory (branches_ might be a very big member variable), the tree0 method will be left in an invalid state containing a copy of leafs_ from tree1 and branches_ that it had before:

struct Leaf { /* ... */ };
struct Branch { /* ... */ };

class OakTree {
public:
auto& operator=(const OakTree& other) {
leafs_ = other.leafs_;
// If copying the branches throws, only the leafs has been
// copied and the OakTree is left in an invalid state
branches_ = other.branches_;
*this;
}
std::vector<Leaf> leafs_;
std::vector<Branch> branches_;
}; auto save_to_disk(const std::vector<OakTree>& trees) {
// Persist all trees ...
}

auto oaktree_func() {
auto tree0 = OakTree{std::vector<Leaf>{1000}, std::vector<Branch>{100}};
auto tree1 = OakTree{std::vector<Leaf>{50}, std::vector<Branch>{5}}
try {
tree0 = tree1;
}
catch(const std::exception& e) {
// tree0 might be broken
save_to_disk({tree0, tree1});
}
}

We want the operation to preserve the valid state of tree0 that it had before the assignment operation so that we can save all our oak trees (pretend we are creating an oak tree generator application) and quit.

This can be fixed by using an idiom called copy-and-swap, which means that we perform the operations that might throw exceptions before we let the application's state be modified by non-throwing swap functions:

class OakTree {
public:
auto& operator=(const OakTree& other) {
// First create local copies without modifying the OakTree objects.
// Copying may throw, but this OakTree will still be in a valid state
auto leafs = other.leafs_;
auto branches = other.branches_;

// No exceptions thrown, we can now safely modify
// the state of this object by non-throwing swap
std::swap(leads_, leafs);
std::swap(branches_, branches);
return *this;
}
std::vector<Leaf> leafs_;
std::vector<Branch> branches_;
};

Resource acquisition

Note that the destructors of all the local variables are still executed, meaning that any resources (in this case, memory) allocated by leafs and branches will be released. The destruction of C++ objects is predictable, meaning that we have full control over when, and in what order, resources that we have acquired are being released. This is further illustrated in the following example, where the mutex variable m is always unlocked when exiting the function as the lock guard releases it when we exit the scope, regardless of how and where we exit:

auto func(std::mutex& m, int val, bool b) {
auto guard = std::lock_guard<std::mutex>{m}; // The mutex is locked
if (b) {
// The guard automatically releases the mutex at early exit
return;
}
if (val == 313) {
// The guard automatically releases if an exception is thrown
throw std::exception{};
}
// The guard automatically releases the mutex at function exit
}

Ownership, lifetime of objects, and resource acquisition are fundamental concepts in C++ which we will cover later on in this book.

Exceptions versus error codes

In the mid 2000s, using exceptions in C++ affected performance negatively, even if they weren't thrown. Performance-critical code was often written using error code return values to indicate exceptions. Bloating the code base with returning error codes and error code handling was simply the only way of writing performance-critical and exception-safe code.

In modern C++ compilers, exceptions only affect the performance when thrown. Considering all the thrown exceptions are rare enough to quit the current process, we can safely use exceptions even in performance-critical systems and benefit from all the advantages of using exceptions instead of error codes.

Libraries used in this book

As mentioned earlier, C++ does not provide more than the bare necessities in terms of libraries. In this book, we will, therefore, have to rely on external libraries where necessary. The most commonly used library in the world of C++ is probably the Boost library (http://www.boost.org). In order to keep the number of used libraries low, we will use the Boost library for hardware-dependent optimizations such as SIMD and GPU.

Throughout this book, we will use the Boost library where the standard C++ library is not enough. Many upcoming parts of the C++ standard are available today in Boost (filesystem, any, optional, and variant), and we will not avoid any libraries planned for inclusion in the C++ standard. We will only use the header-only parts of the Boost library, which means that using them yourself does not require any specific build setup; rather, you just have to include the specified header file.

Summary

In this chapter we have highlighted some features and drawbacks of C++ and how it evolved to the state it is in today.

Further, we discussed the advantages and disadvantages of C++ compared with other languages, both from the perspective of performance and robustness. Hopefully, some myths about C++ and exceptions were dispelled.

You also learned the importance of strict interfaces, resource acquisition, and correct exception handling.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Explore concurrent programming in C++
  • Identify memory management problems
  • Use SIMD and STL containers for performance improvement

Description

C++ is a highly portable language and can be used to write both large-scale applications and performance-critical code. It has evolved over the last few years to become a modern and expressive language. This book will guide you through optimizing the performance of your C++ apps by allowing them to run faster and consume fewer resources on the device they're running on without compromising the readability of your code base. The book begins by helping you measure and identify bottlenecks in a C++ code base. It then moves on by teaching you how to use modern C++ constructs and techniques. You'll see how this affects the way you write code. Next, you'll see the importance of data structure optimization and memory management, and how it can be used efficiently with respect to CPU caches. After that, you'll see how STL algorithm and composable Range V3 should be used to both achieve faster execution and more readable code, followed by how to use STL containers and how to write your own specialized iterators. Moving on, you’ll get hands-on experience in making use of modern C++ metaprogramming and reflection to reduce boilerplate code as well as in working with proxy objects to perform optimizations under the hood. After that, you’ll learn concurrent programming and understand lock-free data structures. The book ends with an overview of parallel algorithms using STL execution policies, Boost Compute, and OpenCL to utilize both the CPU and the GPU.

Who is this book for?

If you're a C++ developer looking to improve the speed of your code or simply wanting to take your skills up to the next level, then this book is perfect for you.

What you will learn

  • Benefits of modern C++ constructs and techniques
  • Identify hardware bottlenecks, such as CPU cache misses, to boost performance
  • Write specialized data structures for performance-critical code
  • Use modern metaprogramming techniques to reduce runtime calculations
  • Achieve efficient memory management using custom memory allocators
  • Reduce boilerplate code using reflection techniques
  • Reap the benefits of lock-free concurrent programming
  • Perform under-the-hood optimizations with preserved readability using proxy objects
  • Gain insights into subtle optimizations used by STL algorithms
  • Utilize the Range V3 library for expressive C++ code
  • Parallelize your code over CPU and GPU, without compromising readability

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Jan 31, 2018
Length: 374 pages
Edition : 1st
Language : English
ISBN-13 : 9781787124776
Category :
Languages :
Tools :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Product Details

Publication date : Jan 31, 2018
Length: 374 pages
Edition : 1st
Language : English
ISBN-13 : 9781787124776
Category :
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 103.97
The Modern C++ Challenge
€29.99
Mastering C++ Multithreading
€36.99
C++ High Performance
€36.99
Total 103.97 Stars icon
Banner background image

Table of Contents

12 Chapters
A Brief Introduction to C++ Chevron down icon Chevron up icon
Modern C++ Concepts Chevron down icon Chevron up icon
Measuring Performance Chevron down icon Chevron up icon
Data Structures Chevron down icon Chevron up icon
A Deeper Look at Iterators Chevron down icon Chevron up icon
STL Algorithms and Beyond Chevron down icon Chevron up icon
Memory Management Chevron down icon Chevron up icon
Metaprogramming and Compile-Time Evaluation Chevron down icon Chevron up icon
Proxy Objects and Lazy Evaluation Chevron down icon Chevron up icon
Concurrency Chevron down icon Chevron up icon
Parallel STL Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.2
(22 Ratings)
5 star 63.6%
4 star 13.6%
3 star 4.5%
2 star 13.6%
1 star 4.5%
Filter icon Filter
Top Reviews

Filter reviews by




Christian Albaret Feb 16, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I bought 5 books at once and could only give a fast glance at this one in order to write he review. This is a highly technical book of almost 400 pages—it takes some time to read that. I am senior developper and work on performant computing in my job. I see that the book handles all the major topics about performance as they occur in present-day computers and as they are programmed in present-day C++ (updated to C++17). The topics are discussed, and complemented with examples, and diagrams to explain memory layout. This will be a good reference book for any developper committed to performant computing, and a good introduction for anyone wanting to start seriously.
Feefo Verified review Feefo
Debasish Aug 14, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Good
Amazon Verified review Amazon
SonChakr May 24, 2018
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Liked the book very much, especially as it is written about software performance from a C++ perspective (true to its title).Chapter 11 is a bonus, as it gives some insight into Boost's compute library, though the content could be elaborated a tad more. Overall, I liked the book and read through it, like a breeze (and refer to it, once in a while).
Amazon Verified review Amazon
Anil Kumar Sep 01, 2018
Full star icon Full star icon Full star icon Full star icon Full star icon 5
The books like 'Effective Modern C++' talk about readable and maintainable code (considering performance as well) using modern C++ but this book primarily focuses on things like: what does that code mean for processor and how to make it more efficient while maintaining the readability. All such suggestions have been supported with performance numbers. It is a huge bonus that this book also uses modern C++. Some features are yet to be included in STL but this book explained them as they were too important to skip (like Ranges library).I loved its content on cache, lazy-evaluation, STL algorithms and reflection. The content presentation is very clear and examples have working modern C++ code.
Amazon Verified review Amazon
Chen Xu May 04, 2018
Full star icon Full star icon Full star icon Full star icon Full star icon 5
The best C++ book I have every read.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.