C++ High Performance

4.3 (10 reviews total)
By Viktor Sehr , Björn Andrist
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. A Brief Introduction to C++

About this book

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.

Publication date:
January 2018
Publisher
Packt
Pages
374
ISBN
9781787120952

 

Chapter 1. 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 notshare 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.

About the Authors

  • Viktor Sehr

    Viktor Sehr is the main developer at Toppluva, working with a highly-optimized graphics engine aimed for mobile hardware.

    He has 10 years of professional experience using C++, with real-time graphics, audio, and architectural design as his focus areas. Through his career, he has developed medical visualization software at Mentice and Raysearch Laboratories as well as real-time audio applications at Propellerhead Software. Viktor holds an M.S. in media science from Linköping University.

    Browse publications by this author
  • Björn Andrist

    Björn Andrist is a freelance software consultant currently focusing on audio applications. For more than 10 years, he has been working professionally with C++ in projects ranging from Unix server applications to real-time audio applications on desktop and mobile. In the past, he has also taught courses in algorithms and data structures, concurrent programming, and programming methodologies. Björn holds a BS in computer engineering and an MS in computer science from KTH Royal Institute of Technology.

    Browse publications by this author

Latest Reviews

(10 reviews total)
I never received the book I ordered
Not having gone through the complete book, the portions I have browsed through are written in clear language, cover some of the most common topics impacting the performance of C++ - obvious but least understood. Book also gets into the syntactical aspect of the language and presents the comparison to other programming languages.
Very good book for improving performance of code.

Recommended For You

Hands-On Design Patterns with C++

A comprehensive guide with extensive coverage on concepts such as OOP, functional programming, generic programming, and STL along with the latest features of C++

By Fedor G. Pikus
Extreme C

Push the limits of what C - and you - can do, with this high-intensity guide to the most advanced capabilities of C

By Kamran Amini
Hands-On System Programming with C++

A hands-on guide to making system programming with C++ easy

By Dr. Rian Quinn
C++ Data Structures and Algorithm Design Principles

Get started with C++ programming by learning how to build applications using its data structures and algorithms

By John Carey and 2 more