【问题标题】:Detect that std::shared_ptr is holding a raw array (and obtaining its size)检测 std::shared_ptr 是否持有原始数组(并获取其大小)
【发布时间】:2020-04-10 17:29:54
【问题描述】:

我正在开发(又一个)支持标准类型(如容器)的 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 成员类型定义中被删除(通过std::remove_extent_t)。我在智能指针 API 中也找不到任何可以显示原始数组大小的内容。

我曾考虑在 operator[]operator* 上使用检测器习语,但如果 T 是或不是原始数组,似乎不需要取消定义它们。

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

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

【问题讨论】:

  • shared_ptr 接口中没有任何内容,为此。
  • “我在智能指针 API 中也找不到任何可以显示原始数组大小的内容。”您不会,因为智能指针无法获取该信息。
  • " 我认为智能指针可以在构造时获取大小信息" 好的,让我们看看 std::shared_ptr&lt;int[]&gt; ptr( new int[10] ); 告诉我在这种情况下共享 ptr 可以获取值 10 的方式
  • 你可以知道std::shared_ptr&lt;T[N]&gt;的大小,但不能知道std::shared_ptr&lt;T[]&gt;的大小。
  • constructor doc (3-7) 当TU[N]U[] 时,有关于参与重载的C++17 注释。

标签: c++ arrays c++17 shared-ptr smart-pointers


【解决方案1】:

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

if constexpr (std::is_array_v<T>)

但是没有办法获取大小,因为它是动态分配的,没有存储在任何地方。

【讨论】:

  • 即使你能得到大小,有些条目可能没有被初始化,所以访问它们很可能是UB。由于很多原因,这只是一个坏主意。
  • 最有趣的是new [] 确实存储了大小,因为delete [] 需要知道要调用多少个析构函数。它只是以未记录的方式存储。例如,请参阅this(不要使用它!!)。
  • @sparik 确实,我在 libc++ 中戳了一下,在 shared_ptr 构造函数重载中找到了 std::is_array。即使element_type 删除了数组类型,我也可以访问T,因此在我的情况下std::is_array&lt;T&gt; 是可能的。唉,正如您和其他人所指出的那样,缺乏规模信息是破坏交易的因素。谢谢!
  • @DavidSchwartz new Foo[10] 不会在所有元素上调用构造函数?或者您的观点是用户在将shared_ptr 传递给序列化程序时可能会“撒谎”正在初始化的元素?
  • @DavidSchwartz 哦,我明白了,用户可以执行 shared_ptr(new Foo[n]) 而不费心检查 n==10,我将无法知道。
【解决方案2】:

正如 Jarod42 所评论的,如果使用 std::shared_ptr&lt;T[N]&gt;(但不是 std::shared_ptr&lt;T[]&gt;),我可以访问原始数组大小。

因此我可以通过模板参数添加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&lt;T&gt; 时无法知道大小(我应该更好地措辞我的问题)。

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

感谢大家的cmets和回答!


附录

根据这个answerunique_ptr&lt;T[N]&gt; 格式不正确,在 GCC 中确实不适合我。

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

unique_ptrshared_ptr 在这方面的行为方式不同,这有点烦人。

【讨论】:

  • 很奇怪的情况。我确信 smart_ptr FAPP 的行为与 unique_ptr 相同,唯一的区别是添加的引用计数。感谢您指出这一点。此外,关于 U[] 与 U[N] 的实际情况,std 文档也不是很清楚。无论如何, smart_ptr 实现似乎知道/访问大小。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多