11.02 Managing Deep Resource Copies, Move Semantics, and Zero-Overhead Lifetime Guarantees
💻 1. Annotated Source Code
#ifndef RULES_DEMO_H #define RULES_DEMO_H #include <iostream> #include <cstring> #include <memory> using namespace std; class RuleOfThree { public: RuleOfThree(const char* text = "default") { data = new char[strlen(text) + 1]; strcpy_s(data, strlen(text) + 1, text); cout << "[Three] Constructed with: " << data << endl; } RuleOfThree(const RuleOfThree& other) { data = new char[strlen(other.data) + 1]; strcpy_s(data, strlen(other.data) + 1, other.data); cout << "[Three] Copied: " << data << endl; } RuleOfThree& operator=(const RuleOfThree& other) { if (this != &other) { delete[] data; data = new char[strlen(other.data) + 1]; strcpy_s(data, strlen(other.data) + 1, other.data); } cout << "[Three] Assigned: " << data << endl; return *this; } ~RuleOfThree() { cout << "[Three] Destroyed: " << data << endl; delete[] data; } private: char* data; }; class RuleOfFive { public: RuleOfFive(const char* text = "default") { data = new char[strlen(text) + 1]; strcpy_s(data, strlen(text) + 1, text); cout << "[Five] Constructed with: " << data << endl; } RuleOfFive(const RuleOfFive& other) { data = new char[strlen(other.data) + 1]; strcpy_s(data, strlen(other.data) + 1, other.data); cout << "[Five] Copied: " << data << endl; } RuleOfFive& operator=(const RuleOfFive& other) { if (this != &other) { delete[] data; data = new char[strlen(other.data) + 1]; strcpy_s(data, strlen(other.data) + 1, other.data); } cout << "[Five] Copy-assigned: " << data << endl; return *this; } RuleOfFive(RuleOfFive&& other) noexcept { data = other.data; other.data = nullptr; cout << "[Five] Moved!" << endl; } RuleOfFive& operator=(RuleOfFive&& other) noexcept { if (this != &other) { delete[] data; data = other.data; other.data = nullptr; } cout << "[Five] Move-assigned!" << endl; return *this; } ~RuleOfFive() { cout << "[Five] Destroyed: " << (data ? data : "null") << endl; delete[] data; } private: char* data; }; class RuleOfZero { public: RuleOfZero(const char* text = "default") { data = make_unique<char[]>(strlen(text) + 1); strcpy_s(data.get(), strlen(text) + 1, text); cout << "[Zero] Constructed with: " << data.get() << endl; } void print() const { cout << "[Zero] Content: " << data.get() << endl; } private: unique_ptr<char[]> data; }; #endif
#include <iostream> #include "RulesDemo.h" int main() { cout << "\n--- Rule of Three ---" << endl; RuleOfThree a("Alpha"); RuleOfThree b = a; b = a; cout << "\n--- Rule of Five ---" << endl; RuleOfFive c("Beta"); RuleOfFive d = move(c); RuleOfFive e; e = move(d); cout << "\n--- Rule of Zero ---" << endl; RuleOfZero f("Gamma"); f.print(); return 0; }
📐 2. Architecture & UML Class Model
📚 3. Core C++ Concepts Deep-Dive
1. The Rule of Three (C++98)
If a class manages a raw resource and defines any of: Destructor, Copy Constructor, or Copy Assignment Operator, it must explicitly implement all three to avoid shallow copy double-free corruption.
2. The Rule of Five (C++11)
With modern move semantics, classes managing resources should also implement the Move Constructor and Move Assignment Operator (or mark them = delete). Move operations transfer raw pointer ownership in $O(1)$ time without re-allocating memory.
3. The Rule of Zero
Design classes that do not directly manage raw resources; use standard RAII wrappers (e.g. std::unique_ptr, std::array). The compiler will automatically generate correct lifetime operations.
⚡ 4. Embedded Systems & Hardware Reality
1. Move Semantics for DMA and Sensor Buffers
In high-speed data acquisition (e.g. 1 MSPS ADC sampling), transferring 1024-byte buffers between an ISR queue and a processing task via copy constructor wastes CPU cycles and SRAM. Implementing move constructors allows instantaneous $O(1)$ pointer swaps with zero buffer copying.
⚠️ MISRA C++:2008 Rule 12-8-1 & AUTOSAR A12-8-1
A copy constructor and copy assignment operator shall be declared for any class that handles dynamic resources or hardware locks. If copying is nonsensical (e.g. a hardware UART peripheral), both copy operations MUST be explicitly deleted (= delete).
💡 5. Production-Ready Embedded Refactoring
Here is an embedded non-copyable, move-only DMA packet buffer compliant with AUTOSAR A12-8-1:
#include <cstdint> #include <utility> class DmaBuffer { public: explicit DmaBuffer(size_t size) : size_(size), data_(new uint8_t[size]) {} ~DmaBuffer() { delete[] data_; } // Disable dangerous copying (Prevents double-free of DMA buffer) DmaBuffer(const DmaBuffer&) = delete; DmaBuffer& operator=(const DmaBuffer&) = delete; // Enable fast zero-copy move operations DmaBuffer(DmaBuffer&& other) noexcept : size_(other.size_), data_(other.data_) { other.size_ = 0; other.data_ = nullptr; } DmaBuffer& operator=(DmaBuffer&& other) noexcept { if (this != &other) { delete[] data_; data_ = other.data_; size_ = other.size_; other.data_ = nullptr; other.size_ = 0; } return *this; } private: size_t size_; uint8_t* data_; };
📝 Knowledge Verification Quiz
Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.
std::vector verify if a type's move constructor is noexcept. If it is not, the container falls back to copying elements during reallocation to preserve the strong exception guarantee.