Lädt...


🔧 unique_ptr<T> - A minimal implementation


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

Introduction

Smart pointers are a fancy wrapper over raw pointers. Smart pointers are RAII compliant. RAII(Resource Acquisition Is Initialization) is a design paradigm and its a concept very emblematic in C++. It's a simple idea - all resources used by a class should be acquired in the constructor. All resources used by a class must be released in the destructor! It turns out that by abiding by the RAII policy, we avoid half-valid states.

The std::unique_ptr in the <memory> header is a smart pointer that excusively owns and manages the objects created dynamically on the heap through an underlying raw pointer. It subsequently disposes off the object, when it goes out of scope and is destroyed.

struct X{};

std::unique_ptr<X> p1 = std::make_unique<X>();  
//std::unique_ptr<X> p2(p1);      // Error when calling copy constructor, 
                                  // p1 is the exclusive owner

std::unique_ptr enforces exclusive ownership using the fact, that it is not copy-constructible or copy-assignable. Note however, that the below code compiles successfully and is valid code.

int* p = new int(10);

std::unique_ptr<int> p1(p);  
std::unique_ptr<int> p2(p);      

The copy constructor and the copy assignment operator of std::unique_ptr<T> are marked delete. It is however, move constructible and move-assignable.

Basic functionalities to expect out of std::unique_ptr<T>

I skimmed through the documentation for std::unique_ptr on cppreference.com. A basic implementation of unique_ptr in less than 200 lines of code should pass the following unit tests:

int main(){
    /* Test Cases*/

    {
        /* Non member function operator== 
           Comparison with another unique_ptr or nullptr 
        dev::unique_ptr<int> ptr1(new int(10));
        dev::unique_ptr<int> ptr2(new int(10));
        assert(ptr1 == ptr1);   
        assert(!(ptr1 == ptr2));
        */ 
    }

    {
        /* Create and access 
        dev::unique_ptr<int> ptr(new int(10));
        assert(!(ptr == nullptr));
        assert(*ptr == 10);
        */
    }

    {
        /* Reset unique_ptr 
           Replaces the managed object with a new one
        dev::unique_ptr<int> ptr(new int(10));
        ptr.reset(new int(20));
        assert(!(ptr == nullptr));
        assert(*ptr == 20);
        */
    }

    {
        /* Release unique_ptr ownership - 
           Returns a pointer to the managed object and releases ownership 
        dev::unique_ptr<double> ptr(new double(3.14));
        double* rawPtr = ptr.release();
        assert(ptr == nullptr);
        assert(rawPtr != nullptr);
        assert(*rawPtr == 3.14);
        */
    }

    {
        /* Non-member function swap
           Swap the managed objects
        dev::unique_ptr<int> ptr1(new int(10));
        dev::unique_ptr<int> ptr2(new int (20));
        swap(ptr1, ptr2);
        assert(*ptr1 == 20);
        assert(*ptr2 == 10);
         */
    }

    {
        /* Get the raw underlying pointer 
        int* x = new int(10);
        dev::unique_ptr<int> ptr(x);
        int* rawPtr = ptr.get();
        assert(*rawPtr == 10);
        assert(rawPtr == x);
        */
    }

    {
        /* operator bool to test if the unique pointer owns an object 
        dev::unique_ptr<int> ptr(new int(42));
        assert(ptr);
        ptr.reset(nullptr);
        assert(!ptr);
        */

    {
        /* indirection operator* to dereference pointer to managed object,
           member access operator -> to call member function
        struct X{
            int n;
            int foo(){ return n; }
        };


        dev::unique_ptr<X> ptr(new X(10));
        assert((*ptr).n == 10);
        assert(ptr->foo() == 10);
        */ 
    }

    {
        /* Constructs an object of type T and wraps it in a unique_ptr.
        struct Point3D{
            double x, y, z;

            Point3D() {}

            Point3D(double x_, double y_, double z_) : x(x_), y(y_), z(z_) {}

            Point3D(const Point3D& other) : x(other.x), y(other.y), z(other.z) {}

            Point3D(Point3D&& other) 
            : x(std::move(other.x))
            , y(std::move(other.y))
            , z(std::move(other.z))
            {}
        };

        // Use the default constructor. 
        dev::unique_ptr<Point3D> v1 = dev::make_unique<Point3D>();
        // Use the constructor that matches these arguments.
        dev::unique_ptr<Point3D> v2 = dev::make_unique<Point3D>(0, 1, 2);
        // Create a unique_ptr to a vector of 5 elements.
        //dev::unique_ptr<std::vector<int>> v3 = dev::make_unique<std::vector<int>>({1,2,3,4,5});
        */

    }
}

A custom implementation

#include <iostream>
#include <memory>
#include <cassert>
#include <utility>
#include <vector>

namespace dev{
    // Implement unique_ptr here
    template<typename T>
    class unique_ptr{
        public:
        // Default c'tor 
        unique_ptr() : ptr{nullptr} {}

        // Copy c'tor should be deleted to enforce the concept that this is 
        // an owning pointer
        unique_ptr(const unique_ptr& u) = delete;

        // Copy assignment should also be deleted to enforce the ownership of the 
        // managed object
        unique_ptr& operator=(const unique_ptr& ) = delete;

        // Move constructor
        unique_ptr(unique_ptr&& other) : ptr{nullptr}
        {
            std::swap(ptr, other.ptr);
        }

        // Move assignment operator
        unique_ptr& operator=(unique_ptr&& other){
            if(ptr == other)
                return (*this);

            delete_underlying_ptr();
            ptr = std::exchange(other.ptr,nullptr);
            return (*this);
        }

        // Parameterized construtor
        unique_ptr(T* p) 
        : ptr{p} 
        {}

        // Overload deferencing operator *
        T& operator*(){
            return (*ptr);
        }

        // get the raw pointer
        T* get()
        {
            return ptr;
        }

        /* Reset the unique_ptr */
        void reset(T* other){
            if(ptr == other)
                return;

            delete_underlying_ptr();
            std::swap(ptr, other);
        }

        /* Release unique_ptr ownership */
        T* release(){
            return std::exchange(ptr,nullptr);
        }

        /* Destructor */
        ~unique_ptr(){
            delete_underlying_ptr();
        }

        /* swap - Exchange the contents of ptr1 and ptr2 member-by-member */
        friend void swap(dev::unique_ptr<T>& ptr1, dev::unique_ptr<T>& ptr2) noexcept
        {
            //Re-wire the raw pointers
            std::swap(ptr1.ptr, ptr2.ptr);
        }

        /* operator bool */
        constexpr operator bool(){
            return (ptr != nullptr);
        }

        /* Member access operator - return the underlying pointer */
        T* operator->(){
            return ptr;
        }

        /* Helper function to invoke delete on the underlying raw pointer */
        void delete_underlying_ptr(){
            if(ptr != nullptr){
                delete ptr;
                ptr = nullptr;
            }
        }
        private:
        T* ptr;
    };

    template<typename T, typename... Args>
    T* make_unique(Args&&... args){
        return new T(std::forward<Args>(args)...);
    }
}

/* Non-member functions */
//Overload operator==
template<typename T1, typename T2>
bool operator==(dev::unique_ptr<T1>& lhs, dev::unique_ptr<T2>& rhs){
    return lhs.get() == rhs.get();
}

/*template<typename T1> */
template<typename T1>
bool operator==(dev::unique_ptr<T1>& lhs, std::nullptr_t rhs){
    return lhs.get() == nullptr;
}

//Overload operator!=
template<typename T1, typename T2>
bool operator!=(dev::unique_ptr<T1>& lhs, dev::unique_ptr<T2>& rhs){
    return !(lhs == rhs);
}

template<typename T1>
bool operator!=(dev::unique_ptr<T1>& lhs, std::nullopt_t& rhs){
    return !(lhs == rhs);
}

int main(){
    /* Test Cases*/

    {
        /* Non member function operator== 
           Comparison with another unique_ptr or nullptr */ 
        dev::unique_ptr<int> ptr1(new int(10));
        dev::unique_ptr<int> ptr2(new int(10));
        assert(ptr1 == ptr1);   
        assert(ptr1 != ptr2);
    }

    {
        /* Create and access */
        dev::unique_ptr<int> ptr(new int(10));
        assert(ptr != nullptr);
        assert(*ptr == 10);

    }

    {
        /* Reset unique_ptr 
           Replaces the managed object with a new one*/
        dev::unique_ptr<int> ptr(new int(10));
        ptr.reset(new int(20));
        assert(ptr != nullptr);
        assert(*ptr == 20);

        // Self-reset test
        ptr.reset(ptr.get());
    }

    {
        /* Release unique_ptr ownership - 
           Returns a pointer to the managed object and releases ownership */
        dev::unique_ptr<double> ptr(new double(3.14));
        double* rawPtr = ptr.release();
        assert(ptr == nullptr);
        assert(rawPtr != nullptr);
        assert(*rawPtr == 3.14);

    }

    {
        /* Non-member function swap
           Swap the managed objects */
        dev::unique_ptr<int> ptr1(new int(10));
        dev::unique_ptr<int> ptr2(new int (20));
        swap(ptr1, ptr2);
        assert(*ptr1 == 20);
        assert(*ptr2 == 10);

    }

    {
        /* Get the raw underlying pointer */
        int* x = new int(10);
        dev::unique_ptr<int> ptr(x);
        int* rawPtr = ptr.get();
        assert(*rawPtr == 10);
        assert(rawPtr == x);

    }

    {
        /* operator bool to test if the unique pointer owns an object */
        dev::unique_ptr<int> ptr(new int(42));
        assert(ptr);
        ptr.reset(nullptr);
        assert(!ptr);

    }

    {
        /* indirection operator* to dereference pointer to managed object,
           member access operator -> to call member function*/ 
        struct X{
            int n;
            int foo(){ return n; }
        };

        dev::unique_ptr<X> ptr(new X(10));
        assert((*ptr).n == 10);
        assert(ptr->foo() == 10);

    }

    {
        /* operator[] is used to access a managed array

        const int size = 5; 
        dev::unique_ptr<int[]> fact(new int[size]);

        for (int i = 0; i < size; ++i)
            fact[i] = (i == 0) ? 1 : i * fact[i - 1];

        assert(fact[0] == 1);
        assert(fact[1] == 2);
        assert(fact[3] == 6);
        assert(fact[4] == 24);
        assert(fact[5] == 120);
        */
    }

    {
        /* Constructs an object of type T and wraps it in a unique_ptr.*/
        struct Point3D{
            double x, y, z;

            Point3D() {}

            Point3D(double x_, double y_, double z_) : x(x_), y(y_), z(z_) {}

            Point3D(const Point3D& other) : x(other.x), y(other.y), z(other.z) {}

            Point3D(Point3D&& other) 
            : x(std::move(other.x))
            , y(std::move(other.y))
            , z(std::move(other.z))
            {}
        };

        // Use the default constructor. 
        dev::unique_ptr<Point3D> v1 = dev::make_unique<Point3D>();
        // Use the constructor that matches these arguments.
        dev::unique_ptr<Point3D> v2 = dev::make_unique<Point3D>(0, 1, 2);
        // Create a unique_ptr to a vector of 5 elements.
        //dev::unique_ptr<std::vector<int>> v3 = dev::make_unique<std::vector<int>>({1,2,3,4,5});

    }
}

Compiler Explorer

...

🔧 unique_ptr<T> - A minimal implementation


📈 62.81 Punkte
🔧 Programmierung

🕵️ CVE-2008-0259 | Minimal Design minimal Gallery 0.8 path traversal (EDB-4902 / XFDB-39649)


📈 26.75 Punkte
🕵️ Sicherheitslücken

🐧 tinyfetch, a very minimal system info tool (about 1.3kb) it's so minimal that it has no ascii logo


📈 26.75 Punkte
🐧 Linux Tipps

🐧 tinyfetch, a very minimal 1.3kb fetch. it's so minimal that it has no ascii


📈 26.75 Punkte
🐧 Linux Tipps

🐧 I made an ultra-minimal alternative to playerctl (as if that wasn't already minimal enough)


📈 26.75 Punkte
🐧 Linux Tipps

🐧 [systemd] add minimal varlink implementation


📈 24.49 Punkte
🐧 Linux Tipps

🔧 Implementation of a simple image quality evaluation method based on Java and code implementation


📈 22.23 Punkte
🔧 Programmierung

🐧 What's the best linux distro with minimal power consumption out of the box ?


📈 13.37 Punkte
🐧 Linux Tipps

📰 Samsung Galaxy S9: Display-Kanten weniger gekrümmt, minimal dickere Ränder


📈 13.37 Punkte
📰 IT Nachrichten

📰 Distribution Release: Minimal Linux Live 15-Dec-2019


📈 13.37 Punkte
🐧 Unix Server

🐧 [OC] jfchmotfsdynfetch - The MOST minimal fetch tool that fetches precisely NO information about your PC


📈 13.37 Punkte
🐧 Linux Tipps

📰 Revisiting Recurrent Neural Networks RNNs: Minimal LSTMs and GRUs for Efficient Parallel Training


📈 13.37 Punkte
🔧 AI Nachrichten

🔧 DBOS-Cloud: Minimal Effort Change Data Capture (CDC) Tool


📈 13.37 Punkte
🔧 Programmierung

🔧 How to Enhance the API Response with a Wrapper Class with Additional Information with .NET Minimal API


📈 13.37 Punkte
🔧 Programmierung

🍏 Airmail 5.6.16 - Powerful, minimal email client.


📈 13.37 Punkte
🍏 iOS / Mac OS

🐧 [How-To] Minimal KDE Install on Fedora


📈 13.37 Punkte
🐧 Linux Tipps

📰 Minimal Affect: Als Action-Rollenspiel verpackte Sci-Fi-Parodie im Anflug


📈 13.37 Punkte
📰 IT Nachrichten

🕵️ Minishare Minimal HTTP Server up to 1.4.1 memory corruption [CVE-2004-2271]


📈 13.37 Punkte
🕵️ Sicherheitslücken

🐧 Ultra basic CLI-based qemu-kvm x86_64 client. Great for maximum performance and minimal errors.


📈 13.37 Punkte
🐧 Linux Tipps

🐧 Word Processor TUI for Minimal Linux install?


📈 13.37 Punkte
🐧 Linux Tipps

🔧 Transformer model scales weather forecasting skill with minimal architecture changes


📈 13.37 Punkte
🔧 Programmierung

📰 Dieses Minimal-Smartphone schafft es tatsächlich vom Prototyp zum Produkt


📈 13.37 Punkte
🤖 Android Tipps

🔧 Manual setup for a minimal Storybook


📈 13.37 Punkte
🔧 Programmierung

🔧 A minimal dependency-free translation system for Next.js


📈 13.37 Punkte
🔧 Programmierung

🪟 Waterfield Outback Duo brings gorgeous design to a minimal bag, for a price


📈 13.37 Punkte
🪟 Windows Tipps

📰 Kabel-Internet: Vodafones 500-Mbit/s-Tarif schafft minimal 200 Mbit/s


📈 13.37 Punkte
📰 IT Nachrichten

matomo