Зачем разрешать shared_ptr <T [N]>?

Этот ответ цитирует N4082, который показывает, что предстоящие изменения до std::shared_ptr разрешает варианты T[] и T[N]:

В отличие от частичной специализации unique_ptr для массивов, как shared_ptr<T[]>, так и shared_ptr<T[N]> будут действительны, и оба они приведут к вызову delete[] в управляемом массиве объектов.

 template<class Y> explicit shared_ptr(Y* p);
     

Требуется: Y должен быть полным типом. Выражение delete[] p, когда T является типом массива или delete p, когда T не является типом массива, должно быть хорошо сформировано, должно иметь четко определенное поведение и не должно генерировать исключения. Если T U[N], Y(*)[N] должен быть конвертирован в T*; когда T U[], Y(*)[] должен быть конвертирован в T*; в противном случае Y* должен быть конвертирован в T*.

Если я ошибаюсь, Y(*)[N] может быть сформирован только с помощью адреса массива, который явно не может быть использован или удален с помощью shared_ptr. Я также не вижу никаких указаний на то, что N используется каким-либо образом для обеспечения размера управляемого объекта.

Какова мотивация разрешения синтаксиса T[N]? Получает ли она какую-либо фактическую выгоду, и если да, то как она используется?

Ответ 1

Вы можете получить указатель на право владения вложенным объектом с std::shared_ptr на содержащий объект. Если этот вложенный объект является массивом, и вы хотите получить к нему доступ как тип массива, вам действительно нужно использовать T[N] с подходящими T и N:

#include <functional>
#include <iostream>
#include <iterator>
#include <memory>
#include <queue>
#include <utility>
#include <vector>

using queue = std::queue<std::function<void()>>;

template <typename T>
struct is_range {
    template <typename R> static std::false_type test(R*, ...);
    template <typename R> static std::true_type test(R* r, decltype(std::begin(*r))*);
    static constexpr bool value = decltype(test(std::declval<T*>(), nullptr))();
};

template <typename T>
std::enable_if_t<!is_range<T>::value> process(T const& value) {
    std::cout << "value=" << value << "\n";
}

template <typename T>
std::enable_if_t<is_range<T>::value> process(T const &range) {
    std::cout << "range=[";
    auto it(std::begin(range)), e(std::end(range));
    if (it != e) {
        std::cout << *it;
        while  (++it != e) {
            std::cout << ", " << *it;
        }
    }
    std::cout << "]\n";
}

template <typename P, typename T>
std::function<void()> make_fun(P const& p, T& value) {
    return [ptr = std::shared_ptr<T>(p, &value)]{ process(*ptr); };
                            // here ----^
}

template <typename T, typename... M>
void enqueue(queue& q, std::shared_ptr<T> const& ptr, M... members) {
    (void)std::initializer_list<bool>{
        (q.push(make_fun(ptr, (*ptr).*members)), true)...
        };
}

struct foo {
    template <typename... T>
    foo(int v, T... a): value(v), array{ a... } {}
    int value;
    int array[3];
    std::vector<int> vector;
};

int main() {
    queue q;
    auto ptr = std::make_shared<foo>(1, 2, 3, 4);
    enqueue(q, ptr, &foo::value, &foo::array, &foo::vector);
    while (!q.empty()) {
        q.front()();
        q.pop();
    }
}

В приведенном выше коде q просто прост std::queue<std::function<void()>>, но я надеюсь, вы можете себе представить, что это может быть пул потоков, который выгружает обработку в другой поток. Фактически запланированная обработка также тривиальна, но, опять же, я надеюсь, вы можете себе представить, что на самом деле это значительная часть работы.

Ответ 2

Если я ошибаюсь, Y(*)[N] может быть сформирован только с помощью адреса массива, который явно не может принадлежать или удаляться с помощью shared_ptr.

Не забывайте, что shared_ptr является общей утилитой управления ресурсами и может быть сконструирована с помощью пользовательского deallocator:

template<class Y, class D> shared_ptr(Y* p, D d);

Такой предоставленный пользователем освободитель может выполнить действие, отличное от delete/delete[]. Например, если рассматриваемый массив является массивом дескрипторов файлов, "деллалокатор" может просто закрыть все из них.

В таких случаях shared_ptr не владеет объектом в широко используемом смысле и поэтому может быть привязан к существующему массиву, беря его адрес.