Skip to content

E.A.2 Advanced C++

C++ RAII and ownership map

Advanced C++ in deployment is mostly about one question: who owns the resource, and when is it released?

Create advanced.cpp:

#include <iostream>
#include <memory>
struct Engine {
virtual ~Engine() = default;
virtual float run(float input) = 0;
};
struct CpuEngine : Engine {
float run(float input) override {
return input * 0.84f;
}
};
class Session {
public:
explicit Session(std::unique_ptr<Engine> engine)
: engine_(std::move(engine)) {}
void predict() {
std::cout << "cpu_score=" << engine_->run(1.0f) << "\n";
std::cout << "session_done\n";
}
private:
std::unique_ptr<Engine> engine_;
};
int main() {
Session session(std::make_unique<CpuEngine>());
session.predict();
return 0;
}

Run it:

Terminal window
c++ -std=c++17 advanced.cpp -o advanced
./advanced

Expected output:

Terminal window
cpu_score=0.84
session_done
C++ ideaDeployment meaning
Engine interfaceBusiness code can switch CPU/GPU/runtime backends
std::unique_ptrOnly one owner controls the engine resource
std::moveOwnership is transferred into Session
destructor through virtual ~Engine()Cleanup is safe through the interface
RAIIResource lifetime follows object lifetime

Inference systems often need the same business path to run on different backends: a CPU fallback, a GPU runtime, a quantized engine, or a remote service wrapper. The caller should not care which backend is used; it should only ask an Engine to run.

That is why the example separates three responsibilities:

  • Engine defines the contract.
  • CpuEngine provides one implementation.
  • Session owns exactly one engine and calls it through the contract.

If ownership is unclear, deployment bugs become hard to diagnose: memory can leak, buffers can outlive the runtime, or cleanup can happen in the wrong order. std::unique_ptr and RAII make the lifetime visible in the type system instead of hiding it in comments.

Read the example like a runtime adapter. Session is the stable business path, while CpuEngine is just one backend. In a real deployment, the second backend might wrap TensorRT, ONNX Runtime, OpenVINO, or a remote inference endpoint. The caller should not need to change when the backend changes; only construction should change.

The risk to watch is hidden ownership. If two objects both believe they own the same runtime handle, cleanup becomes fragile. If nobody clearly owns it, memory and file handles can leak. A good deployment note should say who creates the resource, who owns it, who can borrow it, and when it is released.

Add a second engine:

struct FastEngine : Engine {
float run(float input) override {
return input * 0.91f;
}
};

Then replace CpuEngine with FastEngine. The rest of Session should not change.

Operation guide and checkpoints

FastEngine should implement the same Engine interface, so Session only receives a different object at construction time:

Session session(std::make_unique<FastEngine>());

The important learning point is that Session depends on the interface, not on CpuEngine directly. std::unique_ptr makes ownership explicit: the session owns exactly one engine instance, and the resource is released automatically when the session ends.

You pass this lesson when you can explain why Session owns the engine, why unique_ptr is safer than a raw pointer here, and how an interface lets one deployment path swap runtime backends.

Keep this page’s proof of learning as a small evidence card:

Deployment Target
local inference, edge device, model server, or optimization experiment
Artifact
C++ snippet, benchmark, model artifact, serving config, or deployment note
Metric
latency, memory, throughput, model size, accuracy drop, or reliability
Failure Check
ABI/build issue, hardware mismatch, quantization loss, or serving bottleneck
Expected Output
reproducible deployment or optimization evidence, not only theory notes