Как заставить экземпляр интеллектуальных указателей только для класса?

Я работал над тем, чтобы предотвратить использование пользователем класса без интеллектуальных указателей. Таким образом, заставляя их обладать объектом, выделяемым и управляемым интеллектуальными указателями. Чтобы получить такой результат, я пробовал следующее:

#include <memory>
class A
{
private :
    ~A {}
    // To force use of A only with std::unique_ptr
    friend std::default_delete<A>;
};

Эта работа очень хорошо, если вы хотите, чтобы пользователи вашего класса могли манипулировать экземпляром вашего класса с помощью std::unique_ptr. Но это не работает для std::shared_ptr. Поэтому я хотел бы знать, есть ли у вас какие-либо идеи для такого поведения. Единственное решение, которое я нашел, делает следующее (использование friend std::allocator_traits<A>; было недопустимым):

#include <memory>
class A
{
private :
    ~A {}
    // For std::shared_ptr use with g++
    friend __gnu_cxx::new_allocator<A>;
};

Но это решение не переносимо. Возможно, я делаю это неправильно.

Ответ 1

Создайте функцию friend'd factory, которая возвращает std::unique_ptr<A>, и сделайте, чтобы ваш класс не имел доступных конструкторов. Но сделайте деструктор доступным:

#include <memory>

class A;

template <class ...Args>
std::unique_ptr<A> make_A(Args&& ...args);

class A
{
public:
    ~A() = default;
private :
    A() = default;
    A(const A&) = delete;
    A& operator=(const A&) = delete;

    template <class ...Args>
    friend std::unique_ptr<A> make_A(Args&& ...args)
    {
        return std::unique_ptr<A>(new A(std::forward<Args>(args)...));
    }
};

Теперь ваши клиенты могут получить unique_ptr<A>:

std::unique_ptr<A> p1 = make_A();

Но ваши клиенты могут так же легко получить shared_ptr<A>:

std::shared_ptr<A> p2 = make_A();

Потому что std::shared_ptr можно построить из a std::unique_ptr. И если у вас есть пользовательские умные указатели, все, что им нужно сделать для взаимодействия с вашей системой, это создать конструктор, который принимает std::unique_ptr, как и std::shared_ptr, и это очень легко сделать:

template <class T>
class my_smart_ptr
{
    T* ptr_;
public:
    my_smart_ptr(std::unique_ptr<T> p)
        : ptr_(p.release())
    {
    }
    // ...
};

Ответ 2

Поскольку нет общего термина "умный указатель", то, что вы хотите, невозможно.

Что вы можете сделать, это поддержка известного набора интеллектуальных указателей. Обычное решение начинается как ваше, делая ctor или dtor частным, и добавляет функции factory. Это может вернуть экземпляр, заполненный желаемыми интеллектуальными указателями. Если вы просто хотите поддерживать unique_ptr и shared_ptr, это означает две функции factory, вряд ли слишком много. (обратите внимание, что эти указатели позволяют осуществлять контрабанду необработанного указателя через простой интерфейс, поэтому элемент управления не заполнен.)