boost::allocate_unique 产生非默认可构造和非移动可分配 unique_ptrs
boost::allocate_unique yields non-deafult constructible and non-move assignable unique_ptrs
我有一个关于 Boost 的 allocate_unique
的问题。看起来生成的 unique_ptr
s 非常有限——如果不提供删除器(即使是无效删除器),它们不能默认构造为 nullptr
,而且移动分配不起作用。
幸运的是,移动构造确实有效,所以我能够通过调用析构函数和移动构造来解决没有移动赋值的问题。
alloc_deleter
不可移动,因此禁用这些 unique_ptr
的移动分配是否是 Boost 的缺陷?还是我误会了什么?
#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;
}
多态分配器是有状态的,这意味着它们不能默认构造 - 因为它们不知道它们应该使用的内存资源。
这不是 PMR 或唯一指针特有的,它也会在例如在 vector
上使用 Boost Interprocess 分配器 - 您将始终必须为分配器传递初始化程序。
如果你想要一个 global/singleton 内存资源,你显然可以声明一个自定义删除器来编码该常量:
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
工厂 return 类型 (alloc_deleter
) 类型兼容。
You can probably pave hack this, but I think it would probably be best to understand the situation before you decide whether that's worth it. (Hint: I don't think it is, because it is precisely the goal of PMR to type erase the allocator difference, trading in runtime state instead. If you go and move all state into the allocator again, you effectively made it a static allocator again, which is where we would have been without PMR anyways)
其他注意事项
pmr_deleter<Vertex> d(nullptr);
格式错误,因为参数可能永远不会为空。两个编译器都将在 -Wnon-null
处 warn about this,正如 Asan/UBSan 将:
/home/sehe/Projects/Whosebug/test.cpp:18:34: runtime error: null pointer passed as argument 2, which is declared to never be null
这是我围绕 std::unique_ptr<T, boost::alloc_deleter>
的专业化编写的包装器。由 boost::allocate_unique
编辑的唯一指针 return 可隐式转换为包装器。包装器是默认可构造的,move-assignable 并且还有 .get()
return 原始指针而不是 boost 奇特指针类型(需要额外的 .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();
我有一个关于 Boost 的 allocate_unique
的问题。看起来生成的 unique_ptr
s 非常有限——如果不提供删除器(即使是无效删除器),它们不能默认构造为 nullptr
,而且移动分配不起作用。
幸运的是,移动构造确实有效,所以我能够通过调用析构函数和移动构造来解决没有移动赋值的问题。
alloc_deleter
不可移动,因此禁用这些 unique_ptr
的移动分配是否是 Boost 的缺陷?还是我误会了什么?
#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;
}
多态分配器是有状态的,这意味着它们不能默认构造 - 因为它们不知道它们应该使用的内存资源。
这不是 PMR 或唯一指针特有的,它也会在例如在 vector
上使用 Boost Interprocess 分配器 - 您将始终必须为分配器传递初始化程序。
如果你想要一个 global/singleton 内存资源,你显然可以声明一个自定义删除器来编码该常量:
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
工厂 return 类型 (alloc_deleter
) 类型兼容。
You can probably pave hack this, but I think it would probably be best to understand the situation before you decide whether that's worth it. (Hint: I don't think it is, because it is precisely the goal of PMR to type erase the allocator difference, trading in runtime state instead. If you go and move all state into the allocator again, you effectively made it a static allocator again, which is where we would have been without PMR anyways)
其他注意事项
pmr_deleter<Vertex> d(nullptr);
格式错误,因为参数可能永远不会为空。两个编译器都将在 -Wnon-null
处 warn about this,正如 Asan/UBSan 将:
/home/sehe/Projects/Whosebug/test.cpp:18:34: runtime error: null pointer passed as argument 2, which is declared to never be null
这是我围绕 std::unique_ptr<T, boost::alloc_deleter>
的专业化编写的包装器。由 boost::allocate_unique
编辑的唯一指针 return 可隐式转换为包装器。包装器是默认可构造的,move-assignable 并且还有 .get()
return 原始指针而不是 boost 奇特指针类型(需要额外的 .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();