Adventures in Qt: Custom Generic C++ Smart Pointer Template

Based on the smart pointer created by VTK, all data types have been tested and can be imported into your own project.//////////////////////////////////////////////////////////genericsmartpointer.h//////////////////////////////////////////////////////////#pragma once // Prevents multiple inclusions of the header file (compatible with mainstream compilers)#ifndef GENERIC_SMART_POINTER_H // Macro definition guard (compatible with all compilers)#define GENERIC_SMART_POINTER_H#include <atomic> // Include atomic operations library for thread-safe reference counting#include <utility> // Include utility library for perfect forwarding and std::swap#include <cassert> // Include assertion library for runtime checks#include <functional> // For std::hash specialization/** * @brief Generic smart pointer template class * @details Supports any C++ type, implements thread-safe reference counting, interface style aligned with vtkSmartPointer * Features: thread-safe, supports copy/move semantics, STL compatible, prevents misuse of raw pointers, supports const types * @tparam T The template type that the pointer points to (normal type) */template <typename T>class GenericSmartPointer{public: /** * @brief Default constructor * @note Initializes to a null pointer, reference count is nullptr */ GenericSmartPointer() noexcept : m_ptr(nullptr) , m_refCount(nullptr) { } /** * @brief nullptr_t constructor * @note Delegates to the default constructor to unify null pointer initialization logic */ GenericSmartPointer(std::nullptr_t) noexcept : GenericSmartPointer() { } /** * @brief Constructor that accepts a raw pointer * @param ptr Raw pointer (pointing to a dynamically allocated object) * @note The explicit keyword prevents implicit conversion, avoiding accidental conversion of raw pointers to smart pointers */ explicit GenericSmartPointer(T* ptr) noexcept : m_ptr(ptr) { if (m_ptr != nullptr) { // Create a reference count, initial value 1 (relaxed memory order: only count increment/decrement, no dependencies) m_refCount = new std::atomic<int>(1); } } /** * @brief Copy constructor * @param other Another smart pointer object (lvalue) * @note Shares object ownership, reference count atomically +1 */ GenericSmartPointer(const GenericSmartPointer& other) noexcept : m_ptr(other.m_ptr) , m_refCount(other.m_refCount) { if (m_refCount != nullptr) { m_refCount->fetch_add(1, std::memory_order_relaxed); } } /** * @brief Move constructor * @param other Another smart pointer object (rvalue) * @note Transfers object ownership, does not modify reference count, original object is set to null */ GenericSmartPointer(GenericSmartPointer&& other) noexcept : m_ptr(other.m_ptr) , m_refCount(other.m_refCount) { other.m_ptr = nullptr; other.m_refCount = nullptr; } /** * @brief Destructor * @note Calls Reset() to release resources, ensuring correct decrement of reference count and memory release */ ~GenericSmartPointer() noexcept { Reset(); } /** * @brief Copy assignment operator * @param other Another smart pointer object (lvalue) * @return Reference to itself * @note First releases current resources, then shares target object ownership, reference count +1 */ GenericSmartPointer& operator=(const GenericSmartPointer& other) noexcept { if (this != &other) { Reset(); m_ptr = other.m_ptr; m_refCount = other.m_refCount; if (m_refCount != nullptr) { m_refCount->fetch_add(1, std::memory_order_relaxed); } } return *this; } /** * @brief Move assignment operator * @param other Another smart pointer object (rvalue) * @return Reference to itself * @note First releases current resources, then transfers target object ownership, original object is set to null */ GenericSmartPointer& operator=(GenericSmartPointer&& other) noexcept { if (this != &other) { Reset(); m_ptr = other.m_ptr; m_refCount = other.m_refCount; other.m_ptr = nullptr; other.m_refCount = nullptr; } return *this; } /** * @brief nullptr assignment operator * @return Reference to itself * @note Sets the smart pointer to null, releasing currently held resources */ GenericSmartPointer& operator=(std::nullptr_t) noexcept { Reset(); return *this; } /** * @brief Prevents direct assignment of raw pointers * @note Avoids mistakenly assigning raw pointers to smart pointers, leading to double release or memory leaks */ GenericSmartPointer& operator=(T*) noexcept = delete; /** * @brief Gets the raw pointer * @return The raw pointer pointing to the actual object (no ownership transfer) * @note Use with caution: avoid manually releasing or assigning to other smart pointers with the raw pointer */ T* Get() const noexcept { return m_ptr; } /** * @brief Resets the smart pointer * @param ptr Optional new pointer (default is nullptr) * @note 1. Releases currently held resources (reference count -1, if 0 then delete object and count) * 2. If a new pointer is passed, takes ownership (reference count initialized to 1) * Optimization: first disconnect self-reference then handle old resources to avoid duplicate operations in multithreading */ void Reset(T* ptr = nullptr) noexcept { // Save old resources, first disconnect self-reference T* oldPtr = m_ptr; std::atomic<int>* oldRef = m_refCount; m_ptr = nullptr; m_refCount = nullptr; // Handle old resources: reference count -1, if 0 then release if (oldRef != nullptr) { // acquire memory order: ensures that all modifications to the object can be seen during destruction if (oldRef->fetch_sub(1, std::memory_order_acquire) == 1) { delete oldPtr; // Release normal object delete oldRef; // Release reference count } } // Take over new resources (if any) m_ptr = ptr; if (m_ptr != nullptr) { m_refCount = new std::atomic<int>(1); } } /** * @brief STL-style reset function * @param ptr Optional new pointer (default is nullptr) * @note Fully consistent with Reset() functionality, adapts to STL container and algorithm usage habits */ void reset(T* ptr = nullptr) noexcept { Reset(ptr); } /** * @brief Swaps the contents of two smart pointers * @param other The other smart pointer to swap with * @note No memory allocation, O(1) operation, thread-safe (only swaps pointers, does not modify counts) */ void Swap(GenericSmartPointer& other) noexcept { std::swap(m_ptr, other.m_ptr); std::swap(m_refCount, other.m_refCount); } /** * @brief Gets the current reference count * @return The current reference count value (0 indicates null pointer) * @note Mainly for debugging and logging, relaxed memory order does not guarantee real-time across threads */ int GetReferenceCount() const noexcept { return (m_refCount != nullptr) ? m_refCount->load(std::memory_order_relaxed) : 0; } /** * @brief Overloads the dereference operator * @return A reference to the actual object * @note Dereferencing a null pointer will trigger an assertion (debug mode), behavior is undefined in release mode */ T& operator*() const noexcept { assert(m_ptr != nullptr && “Dereferencing null GenericSmartPointer!”); return *m_ptr; } /** * @brief Overloads the member access operator * @return The raw pointer to the actual object * @note Accessing members of a null pointer will trigger an assertion (debug mode), behavior is undefined in release mode */ T* operator->() const noexcept { assert(m_ptr != nullptr && “Accessing member of null GenericSmartPointer!”); return m_ptr; } /** * @brief Type conversion operator (explicit) * @return Whether it is non-null (true=holds object, false=null pointer) * @note Explicit conversion avoids accidental type conversion, supports if (sp), if (!sp) checks */ explicit operator bool() const noexcept { return m_ptr != nullptr; } /** * @brief Comparison operator (compared with another smart pointer) * @param other The other smart pointer to compare * @return Whether they point to the same object */ bool operator==(const GenericSmartPointer& other) const noexcept { return m_ptr == other.m_ptr; } /** * @brief Not equal comparison operator (compared with another smart pointer) * @param other The other smart pointer to compare * @return Whether they point to different objects */ bool operator!=(const GenericSmartPointer& other) const noexcept { return !(*this == other); } /** * @brief Comparison operator (compared with nullptr) * @return Whether it is a null pointer */ bool operator==(std::nullptr_t) const noexcept { return m_ptr == nullptr; } /** * @brief Not equal comparison operator (compared with nullptr) * @return Whether it is a non-null pointer */ bool operator!=(std::nullptr_t) const noexcept { return m_ptr != nullptr; }private: T* m_ptr; // Raw pointer pointing to the actual object std::atomic<int>* m_refCount; // Thread-safe reference count (atomic type, nullptr indicates no count)};/** * @brief Specialization of smart pointer for array types * @details Specifically used to manage dynamic arrays (T[]), uses delete[] for destruction * @tparam T The template type of array elements */template <typename T>class GenericSmartPointer<T[]>{public: /** * @brief Default constructor * @note Initializes to a null array pointer, reference count is nullptr */ GenericSmartPointer() noexcept : m_ptr(nullptr) , m_refCount(nullptr) { } /** * @brief nullptr_t constructor * @note Delegates to the default constructor to unify null pointer initialization */ GenericSmartPointer(std::nullptr_t) noexcept : GenericSmartPointer() { } /** * @brief Constructor that accepts a raw array pointer * @param ptr Raw array pointer (must be memory allocated with new[]) * @note The explicit keyword prevents implicit conversion, avoiding misuse of raw pointers */ explicit GenericSmartPointer(T* ptr) noexcept : m_ptr(ptr) { if (m_ptr != nullptr) { m_refCount = new std::atomic<int>(1); } } /** * @brief Copy constructor * @param other Another array smart pointer object * @note Shares array ownership, reference count +1 */ GenericSmartPointer(const GenericSmartPointer& other) noexcept : m_ptr(other.m_ptr) , m_refCount(other.m_refCount) { if (m_refCount != nullptr) { m_refCount->fetch_add(1, std::memory_order_relaxed); } } /** * @brief Move constructor * @param other Another array smart pointer object (rvalue) * @note Transfers array ownership, original object is set to null */ GenericSmartPointer(GenericSmartPointer&& other) noexcept : m_ptr(other.m_ptr) , m_refCount(other.m_refCount) { other.m_ptr = nullptr; other.m_refCount = nullptr; } /** * @brief Destructor * @note Releases array resources (using delete[]) */ ~GenericSmartPointer() noexcept { Reset(); } /** * @brief Copy assignment operator * @param other Another array smart pointer object * @return Reference to itself */ GenericSmartPointer& operator=(const GenericSmartPointer& other) noexcept { if (this != &other) { Reset(); m_ptr = other.m_ptr; m_refCount = other.m_refCount; if (m_refCount != nullptr) { m_refCount->fetch_add(1, std::memory_order_relaxed); } } return *this; } /** * @brief Move assignment operator * @param other Another array smart pointer object (rvalue) * @return Reference to itself */ GenericSmartPointer& operator=(GenericSmartPointer&& other) noexcept { if (this != &other) { Reset(); m_ptr = other.m_ptr; m_refCount = other.m_refCount; other.m_ptr = nullptr; other.m_refCount = nullptr; } return *this; } /** * @brief nullptr assignment operator * @return Reference to itself */ GenericSmartPointer& operator=(std::nullptr_t) noexcept { Reset(); return *this; } /** * @brief Prevents direct assignment of raw pointers */ GenericSmartPointer& operator=(T*) noexcept = delete; /** * @brief Gets the raw array pointer * @return The raw pointer to the array (no ownership transfer) */ T* Get() const noexcept { return m_ptr; } /** * @brief Resets the array smart pointer * @param ptr New array pointer (must be allocated with new[], default is nullptr) * @note Releases current array (delete[]), takes over new array ownership */ void Reset(T* ptr = nullptr) noexcept { T* oldPtr = m_ptr; std::atomic<int>* oldRef = m_refCount; m_ptr = nullptr; m_refCount = nullptr; if (oldRef != nullptr) { if (oldRef->fetch_sub(1, std::memory_order_acquire) == 1) { delete[] oldPtr; // Array-specific release: delete[] delete oldRef; } } m_ptr = ptr; if (m_ptr != nullptr) { m_refCount = new std::atomic<int>(1); } } /** * @brief STL-style reset function * @param ptr New array pointer (default is nullptr) */ void reset(T* ptr = nullptr) noexcept { Reset(ptr); } /** * @brief Swaps two array smart pointers * @param other The other object to swap with */ void Swap(GenericSmartPointer& other) noexcept { std::swap(m_ptr, other.m_ptr); std::swap(m_refCount, other.m_refCount); } /** * @brief Gets the reference count * @return The reference count value */ int GetReferenceCount() const noexcept { return (m_refCount != nullptr) ? m_refCount->load(std::memory_order_relaxed) : 0; } /** * @brief Array subscript access operator * @param idx Array index (must ensure no out-of-bounds access) * @return Reference to the element at the corresponding index * @note Accessing a null pointer will trigger an assertion */ T& operator[](size_t idx) const noexcept { assert(m_ptr != nullptr && “Accessing null array GenericSmartPointer!”); return m_ptr[idx]; } /** * @brief Type conversion operator (explicit) * @return Whether it is a non-null array */ explicit operator bool() const noexcept { return m_ptr != nullptr; } /** * @brief Comparison operator (with another array smart pointer) */ bool operator==(const GenericSmartPointer& other) const noexcept { return m_ptr == other.m_ptr; } /** * @brief Not equal comparison operator (with another array smart pointer) */ bool operator!=(const GenericSmartPointer& other) const noexcept { return !(*this == other); } /** * @brief Comparison operator with nullptr */ bool operator==(std::nullptr_t) const noexcept { return m_ptr == nullptr; } /** * @brief Not equal comparison operator with nullptr */ bool operator!=(std::nullptr_t) const noexcept { return m_ptr != nullptr; }private: T* m_ptr; // Raw pointer pointing to the array std::atomic<int>* m_refCount; // Thread-safe reference count};/** * @brief Helper function to create smart pointers for normal types * @tparam T The type pointed to by the pointer (supports const) * @tparam Args The types of constructor parameters * @param args Perfect forwarding parameters for the constructor * @return The initialized GenericSmartPointer object * @note Replaces manual new, avoids memory leaks, supports any constructor parameters */template <typename T, typename… Args>GenericSmartPointer<T> MakeGenericSmartPointer(Args&&… args){ return GenericSmartPointer<T>(new T(std::forward<Args>(args)…));}/** * @brief Helper function to create smart pointers for array types * @tparam T The type of array elements (supports const) * @param size The size of the array * @return The initialized array-type GenericSmartPointer object * @note Array elements will be default initialized (built-in types to 0, class types call default constructor) */template <typename T>GenericSmartPointer<T[]> MakeGenericSmartPointerArray(size_t size){ return GenericSmartPointer<T[]>(new T[size]());}/** * @brief Global swap function (normal types) * @tparam T The type pointed to by the pointer * @param lhs The left smart pointer * @param rhs The right smart pointer * @note Compatible with std::swap, calls member Swap() */template <typename T>void swap(GenericSmartPointer<T>& lhs, GenericSmartPointer<T>& rhs) noexcept{ lhs.Swap(rhs);}/** * @brief Global swap function (array types) * @tparam T The type of array elements * @param lhs The left array smart pointer * @param rhs The right array smart pointer */template <typename T>void swap(GenericSmartPointer<T[]>& lhs, GenericSmartPointer<T[]>& rhs) noexcept{ lhs.Swap(rhs);}/** * @brief std::hash specialization (normal types) * @details Supports using GenericSmartPointer as keys in std::unordered_map/unordered_set * @tparam T The type pointed to by the pointer */namespace std{template <typename T>struct hash<GenericSmartPointer<T>>{ size_t operator()(const GenericSmartPointer<T>& sp) const noexcept { // Reuse the hash logic of the raw pointer to ensure key uniqueness return hash<T*>()(sp.Get()); }};/** * @brief std::hash specialization (array types) * @tparam T The type of array elements */template <typename T>struct hash<GenericSmartPointer<T[]>>{ size_t operator()(const GenericSmartPointer<T[]>& sp) const noexcept { return hash<T*>()(sp.Get()); }};} // namespace std#endif // GENERIC_SMART_POINTER_H

Leave a Comment