In brokerage HTS/MTS server and client systems that must process a massive stream of orders and market data around the clock, a memory leak is not just a simple bug — it's a critical flaw that can bring the whole service down.

Back in the C++03 days, it was common for memory to leak because an exception was thrown after a new allocation, or a delete call simply got lost somewhere inside a tangle of conditional branches.

But in modern C++ (C++11 and later), smart pointers built on the RAII (Resource Acquisition Is Initialization) principle let us guarantee "zero memory leaks" as a matter of design. In this post, I'll go over how to use each smart pointer and what to watch out for, from the perspective of real-world HTS/MTS development.


1. The Foundation of Smart Pointers: The RAII Principle

📌 What is the RAII (Resource Acquisition Is Initialization) principle?
It's a pattern that ties ownership of a resource (heap memory, a socket, a file handle, etc.) to a stack-based object. When that stack object goes out of its scope and is destroyed, its destructor is automatically called, safely releasing the underlying heap memory.

2. std::unique_ptr: Exclusive Ownership

The first smart pointer you should reach for in real-world work is std::unique_ptr. As the name implies, **only a single object can hold ownership** of a given piece of heap memory.

⚡ Zero-Overhead Abstraction:
std::unique_ptr has the same memory footprint (8 bytes on a 64-bit system) and performance as a raw pointer. Since it carries no overhead at all, you should default to it as your go-to smart pointer.

Basic Usage and Move Semantics

// [Example: using unique_ptr]
struct OrderPacket {
    long long orderId;
    int price;
};

void ProcessOrder() {
    // 1. Safe construction with std::make_unique (C++14)
    auto pOrder = std::make_unique<OrderPacket>();
    pOrder->orderId = 100234;
    pOrder->price = 75000;

    // 2. Copying is forbidden (compile error)
    // std::unique_ptr<OrderPacket> pCopy = pOrder;

    // 3. Ownership transfer via std::move
    std::vector<std::unique_ptr<OrderPacket>> orderQueue;
    orderQueue.push_back(std::move(pOrder)); // pOrder is now nullptr
} // When the function scope ends, every object inside orderQueue is automatically deleted!

3. std::shared_ptr & std::weak_ptr: Shared Ownership and Avoiding Circular References

When several modules (for example, an execution manager, a screen UI renderer, and a networking module) need to reference the same resource at the same time, use the reference-counting-based std::shared_ptr.

Why You Should Use std::make_shared

// ❌ Bad example: this triggers two separate allocations — one for the object and one for the control block
std::shared_ptr<Session> pSession(new Session());

// ⭕ Good example: allocates the object memory and control block together in a single allocation (better speed and cache efficiency)
auto pSession = std::make_shared<Session>();

The Circular Reference Problem and std::weak_ptr

If two objects hold std::shared_ptr references to each other, their reference counts never drop to zero, so **a memory leak results**. To solve this, we use std::weak_ptr, a weak reference that doesn't affect the reference count.

🚨 How to structure a fix for circular references:
The parent ➔ child direction should hold ownership via std::shared_ptr,
while the child ➔ parent direction should only observe, via std::weak_ptr.
// [Safe way to access through a weak_ptr]
std::weak_ptr<Session> wSession = g_pSharedSession;

// weak_ptr can't be dereferenced directly -> call lock() to obtain a temporary shared_ptr
if (auto pSession = wSession.lock()) {
    // Safe to access the memory as long as pSession stays valid
    pSession->SendHeartbeat();
} else {
    // The target memory has already been destroyed
}

4. Smart Pointer Guidelines for HTS/MTS Performance

📋 Practical guideline summary:
  1. Default to std::unique_ptr: Since it has no overhead, prefer exclusive ownership first unless there's a specific reason not to.
  2. When passing function arguments: Pass std::unique_ptr by value (moving it) only when you're actually transferring ownership; when you just need to reference the object, pass a raw pointer or a C++ reference instead. (Unnecessary shared_ptr copies incur the overhead of atomic increment/decrement operations on the reference count.)
  3. Never call the raw constructor: Always use std::make_unique and std::make_shared instead of new T().

5. Wrapping Up

Smart pointers cleanly shut the door on memory leaks and dangling-pointer risks that arise when a C++ developer forgets a manual delete.

If you clearly separate who owns what, and make it a habit to use `unique_ptr`, `shared_ptr`, and `weak_ptr` in the right place for the right job, you can build a flawless HTS/MTS system.