Enumeration

Basic enum types in C++ are problematic. Take a look at the following example.

  enum class Color { red, green, blue };
enum class TrafficLight { green, yellow, red };
  

Depending on the compiler, this code may not compile or crash during runtime. This is because the enum values red and green are defined twice (TrafficLight shadows Color).

This can be solved by using the enum class syntax.

  enum class Color { red, green, blue };
enum class TrafficLight { green, yellow, red };
  

Enum Class Output Operator

Enum classes do not have a default output operator (unlike integers). The output operator must be custom defined if required. This is done by overloading the << operator, outside the enum class definition!

  enum class Color { red, green, blue };

// keyword friend cannot be used!
std::ostream& operator<<(std::ostream& os, const Color& c) {
    switch (c) {
        case Color::red: return os << "red";
        case Color::green: return os << "green";
        case Color::blue: return os << "blue";
    }
    return os;
}
  

Classes

Classes are a user-defined types. They are used to group data and functions that operate on that data.

C++ has two ways to define classes, struct and class. The only difference between the two is the default access level of members. In struct members are public by default, in class members are private by default.

Class Definition

A class definition consists of the following parts:

  class Person {
    // private by default
    // data members
    std::string m_name;
    int m_age;

    void incAge(); // private member function

public: // public section
    // member functions
    Person(const char name[], int age); // constructor
    std::string getName() const; // getter
    void setAge(int age); // setter
};
  

Sample Classes

Synthesized Contructors

Two constructors are synthesized by the compiler if they are not defined by the programmer.

The move constructor is not synthesized if the copy constructor is defined.

  class Point {
    // Copy constructor
    Point(const Point& point);

    // Move constructor
    Point(Point&& point) == default;
    //Point(Point&& point) == delete; // delete move constructor, if necessary
};

Point p1(1, 2);
Point p2(p1); // copy constructor
Point p3 = p1; // copy constructor (only if not initialized)
  

### Static Attributes Static attributes are shared between all instances of a class.

  class Point {
    static int s_nInstances;
    static const int s_version = 1; // initialization only for const

    Point() {
        ++s_nInstances;
    }

    int getNumberOfInstances() {
        return s_nInstances;
    }
};

int Point::s_nInstances = 0; // initialize
std::cout << Point::getNumberOfInstances() << std::endl; // retrieve
  

Memory Footprint

Sometimes object size is larger than expected. The reason: Padding and alignment.

Let’s see an example.

  #include <cstdlib>

struct S1 {
    uint64_t a; // 8 byte
    uint32_t b; // 4 byte
    uint16_t c; // 2 byte
    uint64_t d; // 8 byte
};

struct S2 {
    uint64_t a; // 8 byte
    uint32_t b; // 4 byte
    uint64_t c; // 8 byte
    uint16_t d; // 2 byte
};

int main() {
    std::cout << sizeof(S1) << std::endl; // 24
    std::cout << sizeof(S2) << std::endl; // 32
}
  

These two structs are identical in terms of the size of their members. However, the size of the structs is different.

Why this is the case can be explained by looking at the memory layout of the structs.

  S1:
| a | a | a | a | a | a | a | a |
| b | b | b | b | c | c | p | p |
| d | d | d | d | d | d | d | d |

S2:
| a | a | a | a | a | a | a | a |
| b | b | b | b | p | p | p | p |
| c | c | c | c | c | c | c | c |
| d | d | p | p | p | p | p | p |
  

Structure Padding

The location of objects in memory is aligned to the size of the CPU registers. This is done to improve performance, because the CPU can only access memory in chunks of the size of the registers and loading is time-intensive.

These registers are usually 32 or 64 bit wide. Therefore, objects are aligned to 4 or 8 byte boundaries.

  struct S1 {
    uint32_t a;         // 4 byte
    uint8_t b, c, d;    // 3 byte + 1 byte padding
    uint16_t e;         // 2 byte + 2 byte padding
    // total: 12 byte
    // (total size has to be a multiple of the largest member)
}
  
  | a | a | a | a | b | c | d | p | e | e | p | p |

p = padding
  

Structure Packing

This behavior can be changed by using the #pragma pack directive.

The pack directive allows the below struct to fit into the expected 22 bytes. (Would also work with 2).

  #pragma pack(1) // set packing to 1 byte
struct S1 {
    uint64_t a; // 8 byte
    uint32_t b; // 4 byte
    uint16_t c; // 2 byte
    uint64_t d; // 8 byte
};
#pragma pack() // reset
  

Resource Allocation is Initialization (RAII)

RAII is a programming pattern used to ensure that resources are properly released. Part of RAII is to ensure that resource acquisition occures during initialization (in the constructor), and that, in case of any error during initialization, the resource is properly released.

When an object is not properly constructed, the destructor is not called, which could lead to memory leaks. To prevent this, the constructor should only succeed if all allocation was successful.