检测到 std::shared_ptr 持有原始数组(并获取其大小)

Detect that std::shared_ptr is holding a raw array (and obtaining its size)

我正在研究(另一个)支持容器等标准类型的 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[] and operator* 上使用检测器习惯用法,但如果 T 是或不是原始数组,似乎不需要取消定义它们的实现。

我正在尝试的是可能的吗?我希望我错过了什么,或者我可以使用一些技巧。

我知道我可以强制用户改用 std::shared_ptr<std::array<N,T>>std::shared_ptr<std::vector<T>>,但我只是想检查一下我是否有可能支持保存原始数组的智能指针。

您可以通过使用编译时类型特征检查 T 是否为数组类型来确定 shared_ptr 是否包含数组类型。它甚至在 std.

中实现
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 在评论中指出,用户可能无法正确初始化数组元素(或使用错误的动态大小),而我的序列化程序无法知道。即使包含原始动态数组的智能指针在我的情况下可以工作,支持它们也可能不是一个好主意。

感谢大家的评论和回答!


附录

根据这个 answerunique_ptr<T[N]> 格式不正确,在 GCC 中确实对我不起作用。

我找到了 P0674R1 (Extending make_shared to Support Arrays) 并且它提供了几个 shared_ptr<T[N]> 的例子,因此假设委员会在通过时没有禁止它似乎是合法的。

有点烦人的是 unique_ptrshared_ptr 在这方面的表现不一样。