boost :: allocate_unique дает конструктивные и неперемещаемые присваиваемые unique_ptrs - PullRequest
1 голос
/ 01 августа 2020

У меня вопрос по поводу allocate_unique от Boost. Похоже, что результирующие unique_ptr довольно ограничены - они не могут быть сконструированы по умолчанию как nullptr без предоставления удаления (даже недопустимого), а также не работает назначение перемещения.

К счастью, конструкция move работает, поэтому я смог обойтись без присваивания перемещения, вызвав деструктор и построив перемещение с использованием нового размещения. отключает назначение перемещения этих unique_ptr s? Или я что-то недопонимаю?

#include <memory>
#include <memory_resource>
#include <boost/smart_ptr/allocate_unique.hpp>
#include <iostream>

using Pma = std::pmr::polymorphic_allocator<std::byte>;
template<typename T> using pmr_deleter = boost::alloc_deleter<T, Pma>;
template<typename T> using pmr_unique_ptr = std::unique_ptr<T, pmr_deleter<T>>;
  
struct Vertex {
    float x = 1;
    float y = 2;
    float z = 3;
};

int main() {
    auto& res = *std::pmr::new_delete_resource();
    pmr_deleter<Vertex> d(nullptr);
    pmr_unique_ptr<Vertex> v_empty(nullptr, d); // will not default construct without deleter??
    pmr_unique_ptr<Vertex> v = boost::allocate_unique<Vertex>(Pma(&res), Vertex{7,8,9});

    // v_empty = std::move(v); // operator=(unique_ptr&&) gets deleted because `alloc_deleter` is not moveable!

    // We can hack in a move like this:
    v_empty.~pmr_unique_ptr<Vertex>();
    new (&v_empty) pmr_unique_ptr<Vertex>(v.get(), v.get_deleter());
    v.release();

    std::cout << v_empty->x << "," << v_empty->y << "," << v_empty->z << std::endl;

    return 0;
}

Ответы [ 2 ]

1 голос
/ 01 августа 2020

Polymorphi c Распределители с отслеживанием состояния, что означает, что они не могут быть сконструированы по умолчанию - потому что они не будут знать о ресурсе памяти, с которым должны работать.

Это не относится к PMR или уникальные указатели, это также может возникнуть, например, при использовании распределителей Boost Interprocess на vector - вам всегда нужно будет передавать инициализатор для распределителя.

Iff, если вам нужен глобальный / одноэлементный ресурс памяти, вы очевидно, можно объявить настраиваемый удалитель, который кодирует эту константу:

template <typename T> struct pmr_deleter : boost::alloc_deleter<T, Pma> {
    pmr_deleter()
            : boost::alloc_deleter<T, Pma>(std::pmr::new_delete_resource()) {}
};

Это позволит конструктору (ам) по умолчанию работать:

pmr_unique_ptr<Vertex> v_empty; // FINE
pmr_unique_ptr<Vertex> v_empty(nullptr); // ALSO FINE

Однако это происходит за счет того, что больше не будучи совместимым с типом allocate_unique, возвращаемым фабрикой (alloc_deleter).

Вы можете , вероятно, проложите это, но я думаю, что, вероятно, будет лучше понять ситуация, прежде чем вы решите, стоит ли оно того. ( Подсказка: я не думаю, что это так, потому что цель PMR состоит в том, чтобы ввести стирание разницы в распределителе, вместо этого торгуя в состоянии выполнения. Если вы go и снова переместите все состояние в распределитель, вы фактически снова превратил его в распределитель stati c, который в любом случае был бы там, где мы были бы без PMR )

Другие примечания

pmr_deleter<Vertex> d(nullptr);

Неправильно сформирован, поскольку аргумент никогда не может быть нулевым. Оба компилятора предупредят об этом в -Wnon-null, так же как Asan / UBSan:

/home/sehe/Projects/stackoverflow/test.cpp:18:34: runtime error: null pointer passed as argument 2, which is declared to never be null
0 голосов
/ 05 августа 2020

Вот оболочка, которую я написал вокруг специализации std::unique_ptr<T, boost::alloc_deleter>. Уникальный указатель, возвращаемый boost::allocate_unique, неявно преобразуется в оболочку. Обертка является конструктивной по умолчанию, с возможностью перемещения, а также имеет .get() возврат необработанного указателя вместо модного типа указателя ускорения (который требует дополнительного .ptr() для получения необработанного указателя).

Единственный недостаток заключается в том, что вы должны использовать оболочку явно вместо, например, auto с boost::allocate_unique.

using Pma = std::pmr::polymorphic_allocator<std::byte>;
template<typename T> using pmr_deleter = boost::alloc_deleter<T, Pma>;
template<typename T> class pmr_unique_ptr : public std::unique_ptr<T, pmr_deleter<T>> {
  public:
    using std::unique_ptr<T, pmr_deleter<T>>::unique_ptr;
    T* get() const { return std::unique_ptr<T, pmr_deleter<T>>::get().ptr(); }
    pmr_unique_ptr() : std::unique_ptr<T, pmr_deleter<T>>(nullptr, pmr_deleter<T>(std::pmr::null_memory_resource())) { }
    pmr_unique_ptr(decltype(nullptr)) : pmr_unique_ptr() { }
    template<typename P>
    pmr_unique_ptr(std::unique_ptr<P, pmr_deleter<P>>&& p)
        : pmr_unique_ptr(static_cast<T*>(p.get().ptr()), *reinterpret_cast<pmr_deleter<T>*>(&p.get_deleter())) {
        p.release();
    }
    template<>
    pmr_unique_ptr(std::unique_ptr<T, pmr_deleter<T>>&& p) : std::unique_ptr<T, pmr_deleter<T>>(std::move(p)) { };
    pmr_unique_ptr(T* p, pmr_deleter<T> d) :  std::unique_ptr<T, pmr_deleter<T>>(boost::detail::sp_alloc_ptr<T,T *>(1, p), d) { };
    pmr_unique_ptr(const pmr_unique_ptr&) = delete;
    pmr_unique_ptr(pmr_unique_ptr&& p) : std::unique_ptr<T, pmr_deleter<T>>(std::move(p)) { }
    template<typename P> operator pmr_unique_ptr<P>() {
        P* basep = static_cast<P*>(get());
        pmr_deleter<P> d(*reinterpret_cast<pmr_deleter<P>*>(&this->get_deleter()));
        this->release();
        return {basep, std::move(d)};
    }
    pmr_unique_ptr& operator=(pmr_unique_ptr&& other) {
        this->std::unique_ptr<T, pmr_deleter<T>>::~unique_ptr();
        new (static_cast<std::unique_ptr<T, pmr_deleter<T>>*>(this)) std::unique_ptr<T, pmr_deleter<T>>(std::move(other));
        return *this;
    }
    template<typename P> pmr_unique_ptr& operator=(std::unique_ptr<P, pmr_deleter<P>>&& p) {
        return operator=(pmr_unique_ptr(pmr_unique_ptr<P>(std::move(p))));
    }
};

Пример, который компилируется:

#include <memory_resource>
#include <boost/smart_ptr/allocate_unique.hpp>

// ... the definitions from above
// ...

  pmr_unique_ptr<int> p;
  pmr_unique_ptr<int> p2 = nullptr;

  p2 = boost::allocate_unique<int>(Pma(std::pmr::new_delete_resource()), 5);
  p = std::move(p2);
  int *rawp = p.get();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...