为什么 std::shared_ptr<T> = std::unique_ptr<T[]> 能够编译,而 std::shared_ptr<T[]> = std::unique_ptr<T[]> 不能?

7

我使用以下输入命令在Coliru中探讨了这个主题:

g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

测试代码可以在这里找到,但我已经将代码贴在下面。我在示例中使用了int作为基本类型。


#include <iostream>
#include <memory>

struct Foo{
    Foo() :
    a_{0}, b_{1}, c_{-1}, combination_{0.5} {}

    int
        a_,
        b_,
        c_;
    double
        combination_;
};

int main()
{
    //int
    //    *unManagedArray = new int[16];
    std::unique_ptr<int[]>
        uniqueArrayOrigin = std::make_unique<int[]>(16);
    std::shared_ptr<int>
            // works but needs call to new
    //  sharedSingleTest{unManagedArray, std::default_delete<int[]>{}}; 
            // works, does not require call to new
        sharedSingleUnique = std::make_unique<int[]>(16);       
            // compilation error (conversion to non-scalar type)
    //  sharedSingleDerived = uniqueArrayOrigin;                

    //  std::shared_ptr<int[]>
                // compilation errors
    //      sharedArrayTest{unManagedArray, std::default_delete<int[]>{}};
                // compilation error (conversion to non-scalar type)
    //      sharedArrayUnique = std::make_unique<int[]>(16);
                // compilation error (conversion to non-scalar type)
    //      sharedArrayDerived = uniqueArrayOrigin;

    std::shared_ptr<Foo>
            // works: specified overload of operator= for shared_ptr
        nonArrayTest = std::make_unique<Foo>(); 

    std::cout << "done!\n";
}

我在SO上寻找答案,但只找到了对于实现std::shared_ptr没有特化的参考文献,这主要是因为没有人就该问题向标准委员会提出适当的建议。我很好奇,因为我会解释第4个重载的operator=,std::shared_ptr<T[]>.operator=(std::unique_ptr<T[], Deleter>&&)在cppreference上表明这种语法是合法的——无论std::shared_ptr的数组类型是否有特化,T[]和T[]都是相同的类型。此外,这种语法似乎仅适用于std::make_unique<T[]>的产物,而不适用于唯一指针对象,这与我的理解相矛盾——难道这些调用不应该是有效的吗?我期望它们之间唯一的区别是第一种情况下函数调用后无效的std::unique_ptr<T[]>。另外,我假设既然存在一种将动态分配的数组构造为shared_ptr的方法,不需要使用new,那么我应该优先选择它,而不是使用更混乱和异常不安全的new T[N]调用吗?
简而言之:
1.operator=在std::shared_ptr<T[]>和std::unique_ptr<T[]>之间根本不起作用,尽管我期望它能够工作。为什么?
2.如果有的话,我会期望从T[]到T的类型转换是唯一指针和共享指针之间编译错误的来源。为什么这能行?
3.operator=在std::shared_ptr<T>和std::make_unique<T[]>之间可以工作,但对于std::unique_ptr<T[]>却无法工作。为什么?
4.在需要动态分配的共享数组的情况下,但我不想使用boost或vector(原因如下),我是否应该调用operator= std::make_unique<T[]>(N)?
为什么我不使用?
  • Boost: 我的公司尚未批准使用Boost,我不知道何时或是否会获得批准使用它。
  • Arrays: 我必须在运行时确定此数组的大小。
  • Vectors: 我正在处理实时信号处理系统,并希望避免额外的指针解除引用。我也试图避免在头文件中包含不必要的库(这是用于读写子系统之间的通信)。然而,如果重要的话,我最终选择了稍后优化(过早优化...)并咬紧牙关。不过问题仍然存在。

4
目前,std::shared_ptr 不支持数组。 - T.C.
请澄清一下:这不仅仅是一个针对数组类型的特化不存在的情况,而是std::shared_ptr主动不支持数组?我曾经以为缺乏特化意味着它会像处理其他任何类型一样处理数组类型。 - jaggedSpire
1个回答

5

§20.8.2.2.1/28:

template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r); 

Remark: This constructor shall not participate in overload resolution unless unique_ptr<Y, D>::pointer is convertible to T*.

然而,unique_ptr<U[]>::pointer实际上是U*,而shared_ptr<U[]>T*U(*)[];而U*无法转换为U(*)[],因此重载从未被考虑。

@Yakk 因此,由于未经专门化的类型为 T[]shared_ptr 的行为不符合我的预期,最好将它们视为不受支持,正如 T.C. 所说,而不是仅仅缺少一些很好的功能补充。 - jaggedSpire
1
@jaggedSpire 没错。还要考虑到 shared_ptr 从不调用 delete[] - Columbo
为什么当使用来自正确专门化的 unique_ptr<T[]> 对象的删除器时,它不会调用正确的删除器?此外,我可以进一步询问为什么从现有的 unique_ptr<T[]>make_unique<T[]> 的产物构造所讨论的 shared_ptr<T> 存在功能上的差异吗? - jaggedSpire
1
@Yakk U(*)[]不会衰减为U**,但是如果您有一个类型为U(*)[]的表达式u,那么*u的类型为U[],它会衰减为U*,并且可以使用**u(或者(*u)[0])来访问元素。一个最小完整的程序,不需要任何动态分配,是在TU 1中的extern int zero[]; int main() { int (*pzero)[] = &zero; return **pzero; },以及在TU 2中的int zero[] = { 0 }; - user743382
2
@jaggedSpire 在类型为unique_ptr<T[]>的lvalue和类型为unique_ptr<T[]>的rvalue之间唯一的区别是赋值。类型可以选择只允许其中一个,而unique_ptr是一种利用这种特性的类型。从lvalue进行赋值通常需要进行复制,而unique_ptr无法被复制,否则指针将不再是唯一的。 - user743382
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接