std::unique...::unique_ptr(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

✨ NAME ✨

std::unique_ptr::unique_ptr β€” std::unique_ptr::unique_ptr, bestie. This is the std::unique...::unique_ptr(3) page from the C++ Standard Libary manual, and we are decoding every single constructor overload together. No cap, it’s giving smart-pointer main character energy. πŸ’…


πŸ¦‹ Synopsis πŸ¦‹

okay so here’s the whole synopsis, and yes every overload, every noexcept, every constexpr since C++23, and every β€œremoved in C++17” note is staying exactly where it belongs:

members of the primary template, unique_ptr<T>
constexpr unique_ptr() noexcept;                         (1)
constexpr unique_ptr( std::nullptr_t ) noexcept;
explicit unique_ptr( pointer p ) noexcept;               (2)
(constexpr since C++23)
unique_ptr( pointer p, /* see below */ d1 ) noexcept;    (3)
(constexpr since C++23)
unique_ptr( pointer p, /* see below */ d2 ) noexcept;    (4)
(constexpr since C++23)
unique_ptr( unique_ptr&& u ) noexcept;                   (5)
(constexpr since C++23)
template< class U, class E >                             (6)
(constexpr since C++23)
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
unique_ptr( const unique_ptr& ) = delete;                (7)
template< class U >                                      (8) (removed in C++17)
unique_ptr( std::auto_ptr<U>&& u ) noexcept;
members of the specialization for arrays,
unique_ptr<T[]>
constexpr unique_ptr() noexcept;                         (1)
constexpr unique_ptr( std::nullptr_t ) noexcept;
template< class U > explicit unique_ptr( U p ) noexcept; (2)
(constexpr since C++23)
template< class U > unique_ptr( U p, /* see below */ d1  (3)
(constexpr since C++23)
) noexcept;
template< class U > unique_ptr( U p, /* see below */ d2  (4)
(constexpr since C++23)
) noexcept;
unique_ptr( unique_ptr&& u ) noexcept;                   (5)
(constexpr since C++23)
template< class U, class E >                             (6)
(constexpr since C++23)
unique_ptr( unique_ptr<U, E>&& u ) noexcept;
unique_ptr( const unique_ptr& ) = delete;                (7)

🫢 The overloads, decoded one by one

  • 1) Constructs a std::unique_ptr that owns nothing. Value-initializes the stored pointer and the stored deleter. Requires that Deleter is DefaultConstructible and that construction does not throw an exception. These overloads participate in overload resolution only if std::is_default_constructible<Deleter>::value is true and Deleter is not a pointer type.
  • 2) Constructs a std::unique_ptr which owns p, initializing the stored pointer with p and value-initializing the stored deleter. Requires that Deleter is DefaultConstructible and that construction does not throw an exception. This overload participates in overload resolution only if std::is_default_constructible<Deleter>::value is true and Deleter is not a pointer type.

This constructor is not selected by class template argument deduction. (since C++17)

  • 3, 4) Constructs a std::unique_ptr object which owns p, initializing the stored pointer with p and initializing a deleter D as below (depends upon whether D is a reference type).

a) If D is non-reference type A, then the signatures are:

unique_ptr(pointer p, const A& d) (1) (requires that Deleter is
noexcept;                             nothrow-CopyConstructible)
unique_ptr(pointer p, A&& d)      (2) (requires that Deleter is
noexcept;                             nothrow-MoveConstructible)

b) If D is an lvalue-reference type A&, then the signatures are:

unique_ptr(pointer p, A& d) noexcept;  (1)
unique_ptr(pointer p, A&& d) = delete; (2)

c) If D is an lvalue-reference type const A&, then the signatures are:

unique_ptr(pointer p, const A& d) noexcept;  (1)
unique_ptr(pointer p, const A&& d) = delete; (2)

In all cases the deleter is initialized from std::forward<decltype(d)>(d). These overloads participate in overload resolution only if std::is_constructible<D, decltype(d)>::value is true.

These two constructors are not selected by class template argument deduction. (since C++17)

  • 2-4) In the specialization for arrays, they behave the same as the constructors that take a pointer parameter in the primary template, except that they additionally do not participate in overload resolution unless one of the following is true:
    • U is the same type as pointer, or
    • U is std::nullptr_t, or
    • pointer is the same type as element_type* and U is some pointer type V* such that V(*)[] is implicitly convertible to element_type(*)[].
  • 5) Constructs a unique_ptr by transferring ownership from u to *this and stores the null pointer in u. This constructor only participates in overload resolution if std::is_move_constructible<Deleter>::value is true. If Deleter is not a reference type, requires that it is nothrow-MoveConstructible (if Deleter is a reference, get_deleter() and u.get_deleter() after move construction reference the same value).
  • 6) Constructs a unique_ptr by transferring ownership from u to *this, where u is constructed with a specified deleter (E). It depends upon whether E is a reference type, as following:
    • a) if E is a reference type, this deleter is copy constructed from u’s deleter (requires that this construction does not throw),
    • b) if E is a non-reference type, this deleter is move constructed from u’s deleter (requires that this construction does not throw).

This constructor only participates in overload resolution if all of the following is true:

  • a) unique_ptr<U, E>::pointer is implicitly convertible to pointer,
  • b) U is not an array type,
  • c) either Deleter is a reference type and E is the same type as D, or Deleter is not a reference type and E is implicitly convertible to D.
  • 6) In the specialization for arrays, it behaves the same as in the primary template, except that it will only participate in overload resolution if all of the following is true:
    • U is an array type,
    • pointer is the same type as element_type*,
    • unique_ptr<U,E>::pointer is the same type as unique_ptr<U,E>::element_type*,
    • unique_ptr<U,E>::element_type(*)[] is convertible to element_type(*)[],
    • either Deleter is a reference type and E is the same type as Deleter, or Deleter is not a reference type and E is implicitly convertible to Deleter.
  • 7) Copy constructor is explicitly deleted. 🚫 No copying, periodt.
  • 8) Constructs a unique_ptr where the stored pointer is initialized with u.release() and the stored deleter is value-initialized. This constructor only participates in overload resolution if U* is implicitly convertible to T* and Deleter is the same type as std::default_delete<T>.
πŸ‘‘ Also: overload 8 is the one marked removed in C++17 up in the synopsis, bestie. Keep that in mind.

πŸ’… Parameters πŸ’…

  • p β€” a pointer to an object to manage
  • d1, d2 β€” a deleter to use to destroy the object
  • u β€” another smart pointer to acquire the ownership from

🌸 Notes 🌸

  • (since C++14) Instead of using the overload (2) together with new, it is often a better idea to use std::make_unique<T>.
  • std::unique_ptr<Derived> is implicitly convertible to std::unique_ptr<Base> through the overload (6) (because both the managed pointer and std::default_delete are implicitly convertible).
  • Because the default constructor is constexpr, static unique_ptrs are initialized as part of static non-local initialization, before any dynamic non-local initialization begins. This makes it safe to use a unique_ptr in a constructor of any static object.
  • (since C++17) There is no class template argument deduction from pointer type because it is impossible to distinguish a pointer obtained from array and non-array forms of new.

πŸŽ€ Example πŸŽ€

// Run this code

#include <iostream>
#include <memory>

struct Foo // object to manage
{
    Foo() { std::cout << "Foo ctor\n"; }
    Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
    Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
    ~Foo() { std::cout << "~Foo dtor\n"; }
};

struct D // deleter
{
    D() {};
    D(const D&) { std::cout << "D copy ctor\n"; }
    D(D&) { std::cout << "D non-const copy ctor\n"; }
    D(D&&) { std::cout << "D move ctor \n"; }
    void operator()(Foo* p) const
    {
        std::cout << "D is deleting a Foo\n";
        delete p;
    };
};

int main()
{
    std::cout << "Example constructor(1)...\n";
    std::unique_ptr<Foo> up1; // up1 is empty
    std::unique_ptr<Foo> up1b(nullptr); // up1b is empty

    std::cout << "Example constructor(2)...\n";
    {
        std::unique_ptr<Foo> up2(new Foo); //up2 now owns a Foo
    } // Foo deleted

    std::cout << "Example constructor(3)...\n";
    D d;
    {   // deleter type is not a reference
        std::unique_ptr<Foo, D> up3(new Foo, d); // deleter copied
    }
    {   // deleter type is a reference
        std::unique_ptr<Foo, D&> up3b(new Foo, d); // up3b holds a reference to d
    }

    std::cout << "Example constructor(4)...\n";
    {   // deleter is not a reference
        std::unique_ptr<Foo, D> up4(new Foo, D()); // deleter moved
    }

    std::cout << "Example constructor(5)...\n";
    {
        std::unique_ptr<Foo> up5a(new Foo);
        std::unique_ptr<Foo> up5b(std::move(up5a)); // ownership transfer
    }

    std::cout << "Example constructor(6)...\n";
    {
        std::unique_ptr<Foo, D> up6a(new Foo, d); // D is copied
        std::unique_ptr<Foo, D> up6b(std::move(up6a)); // D is moved

        std::unique_ptr<Foo, D&> up6c(new Foo, d); // D is a reference
        std::unique_ptr<Foo, D> up6d(std::move(up6c)); // D is copied
    }

#if (__cplusplus < 201703L)
    std::cout << "Example constructor(7)...\n";
    {
        std::auto_ptr<Foo> up7a(new Foo);
        std::unique_ptr<Foo> up7b(std::move(up7a)); // ownership transfer
    }
#endif

    std::cout << "Example array constructor...\n";
    {
        std::unique_ptr<Foo[]> up(new Foo[3]);
    } // three Foo objects deleted
}

✨ Output ✨

okay so here's exactly what it prints when you run it β€” every single line, no skips, no cap:

        Example constructor(1)...
        Example constructor(2)...
        Foo ctor
        ~Foo dtor
        Example constructor(3)...
        Foo ctor
        D copy ctor
        D is deleting a Foo
        ~Foo dtor
        Foo ctor
        D is deleting a Foo
        ~Foo dtor
        Example constructor(4)...
        Foo ctor
        D move ctor
        D is deleting a Foo
        ~Foo dtor
        Example constructor(5)...
        Foo ctor
        ~Foo dtor
        Example constructor(6)...
        Foo ctor
        D copy ctor
        D move ctor
        Foo ctor
        D non-const copy ctor
        D is deleting a Foo
        ~Foo dtor
        D is deleting a Foo
        ~Foo dtor
        Example constructor(7)...
        Foo ctor
        ~Foo dtor
        Example array constructor...
        Foo ctor
        Foo ctor
        Foo ctor
        ~Foo dtor
        ~Foo dtor
        ~Foo dtor

πŸ’€ Defect reports πŸ’€

okay so these behavior-changing defect reports got applied retroactively to previously published C++ standards β€” here's the full receipts, bestie:

  • LWG 2118 Β· applied to C++11 β€” behavior as published: "Constructors of unique_ptr<T[]> rejected qualification conversions." Correct behavior: Accept.
  • LWG 2520 Β· applied to C++11 β€” behavior as published: "unique_ptr<T[]> was accidentally made non-constructible from nullptr_t." Correct behavior: Made constructible.
  • LWG 2801 Β· applied to C++11 β€” behavior as published: "The default constructor was not constrained." Correct behavior: Constrained.
  • LWG 2899 Β· applied to C++11 β€” behavior as published: "The move constructor was not constrained." Correct behavior: Constrained.
  • LWG 2905 Β· applied to C++11 β€” behavior as published: "Constraint on the constructor from a pointer and a deleter was wrong." Correct behavior: Corrected.
  • LWG 2944 Β· applied to C++11 β€” behavior as published: "Some preconditions were accidentally dropped by LWG 2905" Correct behavior: Restored.

that's a wrap on this manpage πŸŽ€ footer tea: it's from cppreference.com, dated 2024.06.10 β€” keep slaying those smart pointers, bestie πŸ’…βœ¨

πŸ“¦ where this page comes from