C++ Best Practices
Struct padding worked out precisely, and the modern-idiom checklist.
Intermediate
double starts at an offset that's a multiple of 8, an
int at a multiple of 4), because the processor reads memory a word at a time,
and misaligned reads are slower or, on some architectures, illegal. The compiler inserts
padding bytes to enforce this, and the struct's overall size is rounded up to a multiple of
its largest member's alignment.
struct Wasteful { // 24 bytes
char a; // 1 byte, then 7 bytes of padding to align b to 8
double b; // 8 bytes
char c; // 1 byte, then 7 bytes of padding to round the struct to a multiple of 8
};
struct Packed { // 16 bytes — same members, reordered
double b; // 8 bytes
char a; // 1 byte
char c; // 1 byte, then 6 bytes of trailing padding
};
std::cout << sizeof(Wasteful); and check. Two more data points worth
knowing: a static member never contributes to sizeof, since there's
exactly one copy shared by the whole class rather than one per instance (see
constexpr & static); and any virtual function adds a
single 8-byte vptr to the object, regardless of how many virtual functions the class
declares (see Inheritance & Polymorphism).
= delete on the copy
constructor and copy assignment operator. Older code (and Meyers' original 1990s-era advice,
predating = delete) achieved the same thing by privately inheriting from a
base class whose copy operations were themselves private:
// modern — C++11 onward
class Connection {
public:
Connection() = default;
Connection(const Connection &) = delete;
Connection & operator=(const Connection &) = delete;
};
// the pre-C++11 idiom, still worth recognizing in older codebases
class Uncopyable {
protected:
Uncopyable() = default;
~Uncopyable() = default;
private:
Uncopyable(const Uncopyable &); // declared, never defined — private, so callers can't use it
Uncopyable & operator=(const Uncopyable &);
};
class Connection : private Uncopyable {}; // inherits the unusable copy operations
| Idiom | Why |
|---|---|
auto | let the compiler deduce the type, especially for verbose iterator/template types — but keep it readable; don't hide an important type from a reader who needs to know it |
| Range-based for | for (const auto &x : container) instead of manual iterator/index loops — shorter, and impossible to get the loop bounds wrong |
enum class | a scoped, strongly-typed enum — Color::red can't silently collide with an unrelated enum's red, and can't implicitly convert to int the way plain enum does |
std::string_view | a non-owning view into a string — pass this instead of const std::string& when a function only reads the string, avoiding a copy/allocation the caller didn't need to pay for |
using over typedef | same purpose, but using also works for template aliases, where typedef syntax gets awkward |
enum class Pet { dog, cat, bird };
enum class Mammal { bear, dog }; // no collision with Pet::dog — enum class is scoped
Pet p = Pet::dog; // must qualify — Pet::dog, never bare "dog"
// int x = p; // error — enum class does NOT implicitly convert to int
using IntVector = std::vector<int>; // fine, same as typedef here
template <typename T> using Vec = std::vector<T>; // using can alias a template; typedef cannot
#define SQUARE(x) ((x) * (x)) // macro — pure text substitution, no type checking, and a classic bug trap:
SQUARE(a + b) // expands to ((a + b) * (a + b)) — the extra parens above save this one
int y = 5;
SQUARE(y++); // expands to ((y++) * (y++)) — y is incremented twice, undefined order
constexpr int square(int x) { return x * x; } // the constexpr replacement: type-checked, no double-evaluation risk
#pragma once covers most of that today), conditional compilation
(#ifdef DEBUG), and stringizing (#x turns the token x
into the string literal "x", used by some logging/assertion macros to print an
expression's source text alongside its value).
boost::optional became std::optional (C++17),
boost::filesystem became std::filesystem (C++17),
boost::any became std::any (C++17). If a codebase still uses the
Boost versions, it's usually either predates that standard, or needs a Boost feature that
never made it into the standard library at all (like boost::signals2 for
observer-pattern-style signal/slot connections, or Boost's more complete
program_options command-line parsing).