我正在开发(另一个(支持容器等标准类型的C++序列化库。我特别想支持智能指针。
C++17引入了对持有原始数组的std::shared_ptr
的支持(它知道在这种情况下调用delete []
(。我需要检测shared_ptr
是否持有原始数组,以便我可以相应地序列化它:
template <typename T>
void serialize(Writer& writer, const std::shared_ptr<T> ptr)
{
// Writer has overloaded operator()
if (ptr)
{
if (holdsRawArray(ptr)) // How to implement this???
{
auto arrayWriter = writer.array(); // RAII
auto size = rawArraySize(ptr); // How to get this???
for (std::size_t i=0; i<size; ++i)
arrayWriter(ptr[i]);
}
else
writer(*ptr);
}
else
writer(null);
}
如何确定C++17智能指针包含原始数组?该信息在element_type
成员typedef中被擦除(通过std::remove_extent_t
(。我在智能指针API中也找不到任何可以显示原始数组大小的内容。
我曾考虑在operator[]
和operator*
上使用检测器习惯用法,但如果T
是或不是原始数组,则似乎不需要实现对它们进行定义。
我尝试的是可能的吗?我希望我错过了什么,或者我可以用一些技巧。
我知道我可以强制用户使用std::shared_ptr<std::array<N,T>>
或std::shared_ptr<std::vector<T>>
,但我只想检查一下我是否可以支持包含原始数组的智能指针。
您可以通过使用编译时类型特征检查T是否为数组类型来确定shared_ptr
是否持有数组类型。它甚至在标准中实现
if constexpr (std::is_array_v<T>)
但是没有办法获得大小,因为它是动态分配的,而不是存储在任何地方。
正如Jarod42所评论的,如果使用std::shared_ptr<T[N]>
(但不使用std::shared_ptr<T[]>
(,我可以访问原始数组大小。
因此,我可以通过模板参数添加serialize
过载来获得大小N
,同时知道shared_ptr
持有一个数组。
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
struct Writer // Toy example
{
template <typename T>
void operator()(const T& v) {std::cout << v << ", ";}
};
struct ArrayWriter // Toy example
{
ArrayWriter() {std::cout << "[";}
~ArrayWriter() noexcept {std::cout << "], ";}
template <typename T>
void operator()(const T& v) {std::cout << v << ", ";}
};
// "Regular" shared_ptr
template <typename T>
void serialize(Writer& writer, const std::shared_ptr<T> ptr)
{
static_assert(!std::is_array_v<T>,
"shared_ptr<T[]> not supported: size unknowable");
if (ptr)
writer(*ptr);
else
writer("null");
}
// shared_ptr holding an array of known size
template <typename T, std::size_t N>
void serialize(Writer& writer, const std::shared_ptr<T[N]> ptr)
{
if (ptr)
{
ArrayWriter arrayWriter;
static constexpr auto size = N;
for (std::size_t i=0; i<size; ++i)
arrayWriter(ptr[i]);
}
else
writer("null");
}
int main()
{
Writer writer;
std::shared_ptr<std::string> s{new std::string{"Hello"}};
std::shared_ptr<int[3]> n{new int[3]}; // Error prone!
std::shared_ptr<float[]> x{new float[5]}; // Size lost
n[0] = 1; n[1] = 2; n[2] = 3;
serialize(writer, s); // Outputs Hello,
serialize(writer, n); // Outputs [1, 2, 3, ],
// serialize(writer, x); // static assertion failure
return 0;
}
工作示例:https://onlinegdb.com/r1R5jv0wI
sparik的回答仍然是正确的,他说当突然给shared_ptr<T>
时无法知道尺寸(我本应该把问题写得更好(。
David Schwartz在评论中指出,用户可能无法正确初始化数组元素(或使用错误的动态大小(,我的序列化程序将无法知道。即使包含原始动态数组的智能指针在我的情况下可以工作,支持它们可能也不是一个好主意。
感谢大家的评论和回答!
附录
根据这个答案,unique_ptr<T[N]>
是不正规的,确实对我在GCC不起作用。
我找到了P0674R1(将make_shared扩展到支持阵列(,它提供了几个shared_ptr<T[N]>
的例子,所以假设委员会在通过它时没有禁止它,这似乎是合法的。
unique_ptr
和shared_ptr
在这方面的行为不一样,这有点烦人。