/* * smart_pointer.h version 4 - Ownership policy added. * * A smart pointer type implementing deep copy. * *------------------------------------------------------------------------------- * SUPPORT FOR OWNERSHIP POLICY ADDED: * * Template parameter order changed to try to fins the best order according to * expected use of default arguments. * * The derivation order of policy classes changed a bit. * * Shorthands for all policy classes typedef:ed. * * The following changes are needed for more complex ownership policy variants, * such as reference counting. * * The copy member function is removed from the storage policy interface and * replaced with swap(). * * Copy initialization can not be done by the storage policy copy constructor, * but must instead be done by the ownership policy clone function. * * Smart pointer copy assignment is radically changed. A local smart_pointer * object is used to solve problems associated with more complex ownership * policy variants. * * A swap member function is added to smart_pointer. In priciple all policies * should have a swap member function, to be called in certain situations, and * the swap of smart_pointer is then responsible to call the swap in each policy * class. The use of the swap function may seem a bit awkward in some situations, * but it eliminates infinite recursion that would occure if std::swap had been * used directly. * *------------------------------------------------------------------------------- * SUPPORT FOR STORAGE POLICY ADDED BEFORE: * * The raw pointer and copy() member function is removed from smart_pointer, * and instead implemented by the storage policy classes, default_storage * and array_storage. * * Template parameter storage_policy, with default_storage as default. * * Access to the pointer is now done via the storage policy interface. * *------------------------------------------------------------------------------- * SUPPORT FOR CHECKING POLICY ADDED BEFORE: * * Template compile_time_error and makro STATIC_CHECK to support for * compile-time error generation and messages. * * Checking policy classes no_check, reject_null_deref, reject_null, * assert_check_deref and assert_check. * * Exception null_pointer_exception, thrown by reject_null_deref and * reject_null. * * Template parameter checking_policy, with no_check as default argument. * *------------------------------------------------------------------------------- * SUPPORT FOR CONVERSION POLICY ADDED BEFORE: * * Template select_type is added and used for selecting one of two types at * compile-time. * * Conversion policy classes allow_conversion and disallow_conversion. * * Template parameter conversion_policy, with disallow_conversion as default * argument. * * Private helper class disallow, to which the raw pointer ptr_ cannot be * converted and therefore disallowing implicit type conversion when used * as return type for the type conversion operator, se below. * * Type conversion operator, converting to either T* or disallow depending * on conversion_policy and selected by using select_type. * *------------------------------------------------------------------------------- */ #ifndef SMART_POINTER_H #define SMART_POINTER_H #include #include /******************************************************************************* * STATIC CHECKING ******************************************************************************/ /* * compile_time_error have only a declaration for its unspecialized version, and * a definition only for a true specialization. This will generate a compile-time * error, if an attempt is made to instantiate for false. */ template struct compile_time_error; template <> struct compile_time_error {}; /* * STATIC_CHECK generates a compile-time error if expr is equal to 0. Parameter * msg will show in the compiler error message, which hopefully will give a hint * why the error really occured. */ #define STATIC_CHECK(expr, msg) { compile_time_error<((expr) != 0)> ERROR_##msg; } /******************************************************************************* * TYPE SELECTION ******************************************************************************/ /* * select_type selects one of two types based upon a boolean constant. flag is a * compile-time boolean constant, and if 'true', type T1 is selected, else type * T2 is selected. * * The primary is instantiated for flag equals true, since a specialization for * flag equals false is defined. */ template struct select_type { typedef T1 result; }; template struct select_type { typedef T2 result; }; /******************************************************************************* * CONVERSION POLICY ******************************************************************************/ /* * allow_conversion allow conversion to the pointee type. */ struct allow_conversion { static const bool allow{ true }; }; /* * disallow_conversion does not allow conversion to the pointee type. */ struct disallow_conversion { static const bool allow{ false }; }; /******************************************************************************* * CHECKING POLICY ******************************************************************************/ /* * no_check does not perform any checks. */ struct no_check { template static void on_default(const P) {} template static void on_init(const P) {} template static void on_dereference(const P) {} }; /* * null_pointer_exception is used by some implementations of checking_policy. */ class null_pointer_exception : public std::runtime_error { public: explicit null_pointer_exception() noexcept : std::runtime_error("null pointer exception") {} }; /* * reject_null_dereference throws null_pointer_exception if an attempt is made to * dereference or indirect a null smart pointer. */ struct reject_null_dereference { template static void on_default(const P) {} template static void on_init(const P) {} template static void on_dereference(const P p) { if (!p) throw null_pointer_exception(); } }; /* * reject_null throws null_pointer_exception if an attempt is made to initialize or * assign a smart pointer to null, or to dereference or indirect a null smart pointer. */ struct reject_null { template static void on_default(const P p) { on_init(p); } template static void on_init(const P p) { if (!p) throw null_pointer_exception(); } template static void on_dereference(const P p) { on_init(p); } }; /* * assert_check_dereference checks the pointer before dereference and indirect. */ struct assert_check_dereference { template static void on_default(const P) {} template static void on_init(const P) {} template static void on_dereference(const P p) { assert(p); } }; /* * assert_check_dereference checks the pointer before initialization, assignment, * dereference and indirect. */ struct assert_check { template static void on_default(const P p) { assert(p); } template static void on_init(const P p) { assert(p); } template static void on_dereference(const P p) { assert(p); } }; /* * assert_check_deref checks the pointer against zero upon initialization and * assignment, and before dereference and indirect. */ /******************************************************************************* * STORAGE POLICY ******************************************************************************/ /* * default_storage implements an interface for operating on the stored type. */ template class default_storage { public: using pointer_type = T*; using reference_type = T&; default_storage() : ptr_{ default_value() } {} default_storage(const pointer_type& p) : ptr_{ p } {} // Initialization of the stored pointer must be done by the ownership // policy clone function. default_storage(const default_storage&) {} reference_type operator*() const { return *ptr_; } pointer_type operator->() const { return ptr_; } friend inline pointer_type get_impl(const default_storage& sp) { return sp.ptr_; } friend inline const pointer_type& get_impl_ref(const default_storage& sp) { return sp.ptr_; } friend inline pointer_type& get_impl_ref(default_storage& sp) { return sp.ptr_; } void swap(default_storage& rhs) { std::swap(ptr_, rhs.ptr_); } protected: void destroy() { delete ptr_; } static pointer_type default_value() { return nullptr; } private: pointer_type ptr_; }; /* * array_storage have a stored type which is supposed to be pointer to array. * Ownership policy must not allow copying. */ template class array_storage { public: using pointer_type = T*; using reference_type = T&; array_storage() : ptr_{ default_value() } {} array_storage(const pointer_type& p) : ptr_{ p } {} // Initialization of the stored pointer must be done by the ownership // policy clone function. array_storage(const array_storage&) {} reference_type operator*() const { return *ptr_; } pointer_type operator->() const { return ptr_; } friend inline pointer_type get_impl(const array_storage& sp) { return sp.ptr_; } friend inline const pointer_type& get_impl_ref(const array_storage& sp) { return sp.ptr_; } friend inline pointer_type& get_impl_ref(array_storage& sp) { return sp.ptr_; } void swap(array_storage& rhs) { std::swap(ptr_, rhs.ptr_); } protected: void destroy() { delete[] ptr_; } static pointer_type default_value() { return nullptr; } private: pointer_type ptr_; }; /******************************************************************************* * OWNERSHIP POLICIES ******************************************************************************/ /* * no_copy does not allow any copying. * Note: The simple solution would be to just not declare clone(). */ template struct no_copy { static P clone(const P&) { // static_assert(false, "This_ownership_policy_disallows_copying"); STATIC_CHECK(false, This_ownership_policy_disallows_copying); } static bool release(const P&) { return true; } void swap(no_copy&) {} enum { DESTRUCTIVE_COPY = false }; }; /* * deep_copy will create a deep copy of the pointee, when a smart pointer is * copied. It is assumed that the pointee class implements a member function * clone(). */ template struct deep_copy { static P clone(const P& p) { return p->clone(); } static bool release(const P&) { return true; } void swap(deep_copy&) {} enum { DESTRUCTIVE_COPY = false }; }; /* * destructive_copy will transfer the ownership to the receiving smart pointer * in copy operations, setting the source smart pointer to null. */ template struct destructive_copy { static P clone(P& p) { P result{ p }; p = P(); return result; } static bool release(const P&) { return true; } void swap(destructive_copy&) {} enum { DESTRUCTIVE_COPY = true }; }; /* * reference_counted allows smart pointers to share an object and keeps a * reference count for each object, to make it possible to detect when the * last reference is to disappear and the object shall be destroyed. */ template class reference_counted { public: reference_counted() { p_count_ = new unsigned int{ 1 }; } reference_counted(const reference_counted& rhs) : p_count_{ rhs.p_count_ } {} P clone(const P& p) { ++*p_count_; return p; } bool release(const P&) { if (! --*p_count_) { delete p_count_; return true; } return false; } void swap(reference_counted& rhs) { std::swap(p_count_, rhs.p_count_); } enum { DESTRUCTIVE_COPY = false }; private: unsigned int* p_count_; }; /******************************************************************************* * SMART POINTER ******************************************************************************/ template class ownership_policy = deep_copy, class conversion_policy = disallow_conversion, class checking_policy = no_check, template class storage_policy = default_storage> class smart_pointer : public storage_policy, public ownership_policy::pointer_type> { public: using SP = storage_policy; using pointer_type = typename SP::pointer_type; using reference_type = typename SP::reference_type; using OP = ownership_policy; using KP = checking_policy; using CP = conversion_policy; // Select copy_arg as 'smart_pointer' for destructive copy, // 'const smart_pointer' for all other ownership policies. // Used in parameter type for copying member functions. using copy_arg = typename select_type::result; smart_pointer() { KP::on_default(get_impl(*this)); } smart_pointer(pointer_type p) : SP{ p } { KP::on_init(get_impl(*this)); } smart_pointer(copy_arg& rhs) : SP{ rhs }, OP{} { get_impl_ref(*this) = OP::clone(get_impl_ref(rhs)); } ~smart_pointer() { if (OP::release(get_impl(*this))) this->destroy(); } smart_pointer& operator=(copy_arg& rhs) & { smart_pointer{ rhs }.swap(*this); return *this; } reference_type operator*() const { KP::on_dereference(get_impl_ref(*this)); return SP::operator*(); } reference_type operator*() { KP::on_dereference(get_impl_ref(*this)); return SP::operator*(); } pointer_type operator->() const { KP::on_dereference(get_impl_ref(*this)); return SP::operator->(); } pointer_type operator->() { KP::on_dereference(get_impl_ref(*this)); return SP::operator->(); } private: // Helper class for disallowing implicit type conversion. class disallowed {}; // If implicit type conversion is allowed, 'implicit_conversion_type' // will be defined as 'pointer_type', else as 'disallowed'. using implicit_conversion_type = typename select_type::result; public: // If implicit type conversion is allowed, 'implicit_conversion_type' // will be 'pointer_type', otherwise 'implicit_conversion_type' will // be 'disallowed'. Since there is no conversion from 'pointer_type' // to 'disallowed', it will result in a compile-time error. operator implicit_conversion_type() const { return get_impl(*this); } private: void swap(smart_pointer& rhs) { OP::swap(rhs); SP::swap(rhs); } }; #endif