Project 11.02 Section 11 ⚡ Embedded Relevance: Critical Rule of 3/5/0 Copy Ctor Move Ctor RAII Double Free DMA Buffers

11.02 Managing Deep Resource Copies, Move Semantics, and Zero-Overhead Lifetime Guarantees

Executive Summary: Mastering resource management under C++11/14. We analyze the Rule of Three, the Rule of Five (move semantics), and the Rule of Zero. We investigate double-free hazards, DMA buffer ownership transfers without SRAM copying, and MISRA C++:2008 Rule 12-8-1 compliance.

💻 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

📐 Rule of Three / Five / Zero Memory Lifecycle Architecture
+ Public - Private # Protected
<<class>> RuleOfFiveResource Manual Resource Handler
-dataPtr : int32_t*
-bufferSize : size_t
+RuleOfFiveResource(size: size_t)
+~RuleOfFiveResource()[1. Destructor]
+RuleOfFiveResource(const RuleOfFiveResource&)[2. Copy Ctor]
+operator=(const RuleOfFiveResource&) : RuleOfFiveResource&[3. Copy Assign]
+RuleOfFiveResource(RuleOfFiveResource&&) : noexcept[4. Move Ctor]
+operator=(RuleOfFiveResource&&) : RuleOfFiveResource&[5. Move Assign]
<<class>> RuleOfZeroResource Modern RAII Idiom
-buffer : std::vector<int32_t> (Automatic RAII Management)
+RuleOfZeroResource() = default

📚 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:

💡 Production-Ready Refactor
#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.

Q1. What catastrophic failure occurs if a class allocating dynamic memory relies on the default compiler-generated copy constructor?
A A compile-time template error.
B A shallow copy is made, causing both objects to share the same pointer and triggering a fatal double-free crash upon destruction.
C The object is automatically converted to an rvalue reference.
D The heap memory is converted to static flash memory.
Detailed Explanation: Default copy constructors perform a member-wise shallow copy. Both instances will point to the same memory block, and when both destructors run, the second will attempt to delete already-freed memory (double-free vulnerability).
Q2. Why should move constructors and move assignment operators always be marked `noexcept` in embedded C++?
A To allow standard containers like std::vector to use move operations instead of falling back to expensive copy operations during reallocation.
B To disable compiler optimization.
C Because embedded microcontrollers do not support exceptions in any form.
D To force the object to be placed in ROM.
Detailed Explanation: Containers like 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.
Q3. What is the 'Rule of Zero'?
A Never write classes with more than zero member variables.
B Classes should rely on RAII member types (like smart pointers) so they do not need custom copy/move/destructor functions.
C All member pointers must be initialized to 0 (NULL).
D A function must take zero arguments to be real-time safe.
Detailed Explanation: The Rule of Zero states that if a class is composed of types that already manage their own resources cleanly (e.g. smart pointers, standard containers), the class itself does not need custom special member functions.