Best Practices and Coding Standards — C++ Core Guidelines, Naming Conventions, Code Review, Modern C++ Style
In this tutorial, you will learn about Best Practices and Coding Standards. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ best practices — guided by the C++ Core Guidelines and Bjarne Stroustrup's philosophy — emphasize RAII, value semantics, const correctness, smart pointers, and clear conventions for writing safe, readable, and maintainable code.
What You'll Learn
You will follow the C++ Core Guidelines for safer code, apply naming conventions (PascalCase for types, snake_case for functions/variables), use const correctness and noexcept where appropriate, write modern C++ using RAII, smart pointers, and algorithms, avoid common pitfalls (raw new/delete, C-style casts, macros), conduct effective code reviews, and use clang-tidy and sanitizers for automated enforcement.
Why It Matters
C++ gives you immense power and zero safety nets — a single uninitialized pointer can crash the program. Coding standards replace individual intuition with collective wisdom, preventing entire classes of bugs. C++ codebases that follow standards have fewer crashes, faster onboarding, and lower maintenance costs.
Learning Path
graph LR
A["68: Performance Profiling"] --> B["69: Best Practices"]
B --> C["70: Final Capstone Project"]
C --> D["Done!"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
The C++ Core Guidelines
The Core Guidelines (isocpp.github.io/CppCoreGuidelines) are the definitive reference. Key rules:
Rule | Summary
------------------------|--------
P.1: Express ideas | Code should reflect intent directly
P.5: Prefer compile-time| Catch errors at compile time when possible
P.8: Don't leak | Use RAII for all resources
I.1: Interfaces | Make interfaces explicit
I.30: Encapsulate | Minimize exposure of internal details
F.15: Pass by ref | Prefer const& for input parameters
F.21: Return multiple | Return struct or tuple instead of out-params
C.1: Organize | Class organizes related data + behavior
C.20: Default operations| Follow Rule of Zero or Rule of Five
C.41: Constructor | Constructors should create fully initialized objects
C.80: RAII | Use RAII wrappers for resource management
ES.1: Prefer the STL | Use standard library instead of hand-rolled code
ES.20: Always initialize| Initialize all variables
CP.1: Assume data races| Assume code is multi-threaded; protect shared data
Naming Conventions
Consistent naming improves readability dramatically.
// Types: PascalCase
class BankAccount {};
struct Point2D {};
enum class Color {};
using StringList = std::vector<std::string>;
template <typename ValueType>
class Container {};
// Functions, variables, members: snake_case
int get_account_balance() const;
double calculate_total(const std::vector<double>& prices);
std::string user_name; // local variable
int file_descriptor_; // class member (trailing _)
static constexpr int k_max_size = 1024; // constants: k prefix
// Macros: UPPER_SNAKE_CASE (minimize use)
#define DEBUG_LOG(msg) std::cerr << msg << "\n"
#define MAX_BUFFER_SIZE 4096
// Template parameters: PascalCase or Capital letters
template <typename T, typename Allocator>
class Vector;
// Namespaces: lowercase
namespace networking { namespace http { class Request {}; }}
using namespace networking::http;
Const Correctness
Use const wherever a value should not be modified.
#include <iostream>
#include <vector>
#include <string>
// Const parameters
void print(const std::string& text, int times); // text won't be modified
// Const member functions
class Person {
std::string name_;
int age_;
public:
// Getter: const because it doesn't modify the object
const std::string& name() const { return name_; }
int age() const { return age_; }
// Setter: not const
void set_age(int age) { age_ = age; }
};
// Const iterators for read-only access
void print_all(const std::vector<int>& v) {
for (const auto& elem : v) {
std::cout << elem << " ";
}
}
// Const pointers vs pointer to const
const int* ptr_to_const; // Cannot modify what ptr points to
int* const const_ptr; // Cannot modify ptr itself
const int* const const_ptr_to_const; // Both
// Constexpr for compile-time values
constexpr double PI = 3.14159;
constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1); }
Modern C++ vs Old C++
// 1. Smart pointers vs raw pointers
// OLD:
Object* ptr = new Object();
delete ptr;
// MODERN:
auto ptr = std::make_unique<Object>(); // Unique ownership
auto shared = std::make_shared<Object>(); // Shared ownership
// 2. Algorithms vs manual loops
// OLD:
std::vector<int> v = {3, 1, 4, 1, 5};
std::vector<int> evens;
for (size_t i = 0; i < v.size(); ++i) {
if (v[i] % 2 == 0) evens.push_back(v[i]);
}
// MODERN:
auto evens = v | std::views::filter([](int x) { return x % 2 == 0; });
// 3. nullptr vs NULL/0
// OLD: int* ptr = NULL;
// MODERN:
int* ptr = nullptr;
// 4. Range-for vs index
// OLD:
for (int i = 0; i < v.size(); ++i) std::cout << v[i];
// MODERN:
for (const auto& elem : v) std::cout << elem;
// 5. Override keyword
class Base { virtual void func() {}; };
class Derived : public Base {
// OLD: void func();
// MODERN:
void func() override; // Compiler checks it overrides
};
// 6. enum class vs plain enum
// OLD:
enum Color { RED, GREEN, BLUE }; // Pollutes namespace
// MODERN:
enum class Color { Red, Green, Blue }; // Scoped
// 7. auto for type deduction
// OLD:
std::vector<int>::iterator it = v.begin();
// MODERN:
auto it = v.begin();
// 8. Lambda vs functor
// OLD: struct Adder { int operator()(int a, int b) { return a + b; } };
// MODERN:
auto adder = [](int a, int b) { return a + b; };
What NOT to Do
// 1. No raw new/delete
void bad() {
auto* p = new int(5);
// ... if something throws: leak!
delete p;
}
// 2. No C-style casts
double pi = 3.14;
// int x = (int)pi; // BAD
int x = static_cast<int>(pi); // GOOD
// 3. No #define for constants
// #define MAX_SIZE 100 // BAD
constexpr int k_max_size = 100; // GOOD
// 4. No using namespace std in headers
using namespace std; // BAD in headers — pollutes all includers
// 5. No macros for functions
// #define SQUARE(x) x*x // BAD (x+1)*(x+1)? No!
template <typename T>
constexpr T square(T x) { return x * x; } // GOOD
// 6. No trailing underscores for local variables
int count_; // BAD: looks like member variable
int count; // GOOD
// 7. No global mutable state (unless absolutely necessary)
int global_counter = 0; // BAD: thread-unsafe, unpredictable
// 8. No manual memory management
// std::vector<int>* vec = new std::vector<int>(); // BAD
std::vector<int> vec; // GOOD
Code Review Checklist
A systematic checklist for reviewing C++ code:
[ ] Security: Any buffer overflows? Format string bugs? Integer overflow?
[ ] Lifetime: Any dangling pointers/references? Use-after-free?
[ ] Ownership: Who owns this resource? unique_ptr vs shared_ptr?
[ ] RAII: Are resources wrapped in RAII objects?
[ ] Exception safety: What happens if an exception is thrown?
[ ] Const correctness: Should parameters/methods be const?
[ ] noexcept: Should this function be noexcept?
[ ] Move semantics: Can we avoid copies?
[ ] Includes: Minimize, use forward declarations where possible
[ ] Naming: Does it follow project conventions?
[ ] Override: Are virtual overrides marked override?
[ ] Complexity: Can this be simplified?
[ ] Testing: Is there a unit test for this code?
[ ] Documentation: Is the intent clear?
[ ] Performance: Is there a faster/easier approach?
Automated Tools
# clang-tidy — static analysis
clang-tidy --checks='modernize-*,cppcoreguidelines-*' source.cpp
# clang-format — code formatting
clang-format -i --style=Google source.cpp
# AddressSanitizer (ASan) — memory errors
g++ -fsanitize=address -g -O1 -o program program.cpp
# UndefinedBehaviorSanitizer (UBSan)
g++ -fsanitize=undefined -g -O1 -o program program.cpp
# ThreadSanitizer (TSan) — data races
g++ -fsanitize=thread -g -O1 -o program -lpthread program.cpp
# Valgrind — comprehensive memory analysis
valgrind --leak-check=full ./program
# Include What You Use
iwyu source.cpp
// .clang-format — Google style
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
AllowShortFunctionsOnASingleLine: Inline
Common Mistake Patterns
| Pattern | Problem | Fix |
|---|---|---|
new/delete |
Leaks on exception | unique_ptr / make_unique |
#define CONST |
No type safety | constexpr variable |
(int)cast |
Hard to find, unsafe | static_cast<int> |
char buf[100] |
Buffer overflow | std::string / std::array |
if (p = nullptr) |
Assignment instead of == | Compile with -Wall |
throw in dtor |
terminate() if unwinding |
Never throw from dtor |
virtual without override |
Silent overload slip | Add override |
unsigned for loop |
Infinite loop on decrement | Use signed or size_t |
std::bind |
Verbose, lambda is better | Use lambda |
this-> everywhere |
Noise | Use only when needed (templates) |
Practice Questions
What is the most important C++ best practice? Answer: Use RAII for all resource management. It prevents leaks for memory, files, locks, and every other resource.
Why should you avoid #define for constants and functions? Answer: Macros have no type safety, no scope, and can cause subtle bugs (double evaluation, precedence issues). Use constexpr instead.
What does the
overridekeyword do? Answer: It tells the compiler that the function is meant to override a virtual function. If it doesn't, the compiler produces an error.When should you use unique_ptr vs shared_ptr? Answer: unique_ptr for exclusive ownership (default). shared_ptr for truly shared ownership when the last owner's lifetime is unknown.
Name three sanitizers and what they detect. Answer: AddressSanitizer (memory errors), UndefinedBehaviorSanitizer (UB), ThreadSanitizer (data races).
FAQ
Mini Project
Review this buggy code against the C++ Core Guidelines and fix all violations:
// review_me.cpp — find and fix all guideline violations
#include <iostream>
#include <vector>
#include <string>
#define MAX_SIZE 100
class data {
public:
char* buffer;
int size;
data(int s) { buffer = new char[s]; size = s; }
~data() { delete buffer; }
};
int* get_ptr() {
int x = 42;
return &x;
}
void process(data d) {
for (int i = 0; i <= d.size; i++) {
d.buffer[i] = 'a';
}
}
int main() {
data d(10);
process(d);
int* p = get_ptr();
std::cout << *p << "\n";
std::vector<int> v;
for (int i = 0; i < 100; i++) v[i] = i;
return 0;
}
Violations to find:
- Macro instead of constexpr
- PascalCase for class names
- Raw new/delete (RAII violation)
- Dangling pointer (returning address of local)
- Off-by-one and missing null terminator
- No copy constructor (double delete)
- Wrong delete (should be delete[])
- Vector out-of-bounds access
- Missing virtual destructor
- Pass by value instead of const ref
This exercise demonstrates how C++ best practices prevent real bugs. Compare with Java where memory management and dangling pointers are non-issues due to Garbage Collection.
What's Next
You now know C++ best practices and coding standards. Next, you will apply everything you've learned in the final capstone project — building a complete C++ application from scratch.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro