2016-11-06 87 views
20

This answer援引N4082,其示出了即将对std::shared_ptr变化将允许T[]T[N]的变体:为什么要让shared_ptr <T[N]>?

不同于用于阵列unique_ptr部分特,既shared_ptr<T[]>shared_ptr<T[N]>将是有效的,都将导致delete[]是调用托管的对象数组。

template<class Y> explicit shared_ptr(Y* p); 

需要Y应是一个完整的类型。表达式delete[] p,当T是数组类型时,或delete p,当T不是数组类型时,应该是格式良好的,应该有良好定义的行为,并且不会抛出异常。当TU[N]时,Y(*)[N]应转换为T*;当TU[]时,Y(*)[]应转换为T*;否则,Y*应转换为T*

除非我记错了,一个Y(*)[N]只能采取一个数组,这显然不能拥有或由shared_ptr删除的地址形成。我也看不到有任何迹象表明N以任何方式被用于强制执行被管理对象的大小。

允许T[N]语法背后的动机是什么?它是否会产生任何实际利益?如果是,它是如何使用的?

+3

没有任何东西可以保护您免受误用,但是一旦您拥有'shared_ptr ',您可以遍历其元素而无需其他信息,所以就是这样。请注意,您仍然使用指向第一个元素的指针构造它,而不是指向数组本身的指针,因为您应该从'new T [N]'中获取指针。 –

+0

@KerrekSB“... *因为你想要得到来自新的* T [N]的指针'”并不总是。请参阅[我的答案](http://stackoverflow.com/a/40447905/6394138)。 – Leon

+0

当我进一步回顾N4082时,我发现'operator []'表示'i

回答

2

除非我记错了,一个Y(*)[N]只能采取一个数组,这显然不能拥有或由shared_ptr删除的地址 形成。

不要忘记shared_ptr是一个通用的资源管理工具,并可以用自定义释放器构造:

template<class Y, class D> shared_ptr(Y* p, D d); 

这样的用户提供释放器可以比delete/delete[]执行其他的操作。例如,如果问题数组是一个文件描述符数组,“deallocator”可以关闭所有这些数组。

在这种情况下,shared_ptr不拥有广泛使用的意义上的对象,因此可以通过获取其地址绑定到现有的数组。

+0

标准中是否有任何文字证明“'shared_ptr'是通用资源管理工具”?正如我一直理解的那样,'shared_ptr'意味着所有权合同。需要允许自定义删除器意味着合同不能被严格执行,但我一直认为它具有非拥有'shared_ptr'的错误。 –

+1

@ monkey_05_06对定制释放器的支持使其成为通用资源管理工具。你只知道一个共享指针,指向一个对象的最后一个指针将在删除该引用之前执行清理操作(默认情况下删除该对象,但原则上可以是任何东西)。 – Leon

+0

也许你可以说这是一个语义参数,但像std :: get_deleter这样的方法的存在表明'delete'操作是'shared_ptr'的合约部分(进一步受到'operator delete'这个事实的支持默认删除者)。我并不是在争论你是否可以产生非拥有的shared_ptr,但我认为这样做在实践中是一件坏事。我觉得它混淆了使用'shared_ptr'作为引用包装的代码。 –

6

您可以获取一个指向共享所有权的嵌套对象的指针,std::shared_ptr指向包含对象。如果该嵌套的对象恰好是一个数组,你要访问它作为一个数组类型,你实际上需要用合适的TN使用T[N]

#include <functional> 
#include <iostream> 
#include <iterator> 
#include <memory> 
#include <queue> 
#include <utility> 
#include <vector> 

using queue = std::queue<std::function<void()>>; 

template <typename T> 
struct is_range { 
    template <typename R> static std::false_type test(R*, ...); 
    template <typename R> static std::true_type test(R* r, decltype(std::begin(*r))*); 
    static constexpr bool value = decltype(test(std::declval<T*>(), nullptr))(); 
}; 

template <typename T> 
std::enable_if_t<!is_range<T>::value> process(T const& value) { 
    std::cout << "value=" << value << "\n"; 
} 

template <typename T> 
std::enable_if_t<is_range<T>::value> process(T const &range) { 
    std::cout << "range=["; 
    auto it(std::begin(range)), e(std::end(range)); 
    if (it != e) { 
     std::cout << *it; 
     while (++it != e) { 
      std::cout << ", " << *it; 
     } 
    } 
    std::cout << "]\n"; 
} 

template <typename P, typename T> 
std::function<void()> make_fun(P const& p, T& value) { 
    return [ptr = std::shared_ptr<T>(p, &value)]{ process(*ptr); }; 
          // here ----^ 
} 

template <typename T, typename... M> 
void enqueue(queue& q, std::shared_ptr<T> const& ptr, M... members) { 
    (void)std::initializer_list<bool>{ 
     (q.push(make_fun(ptr, (*ptr).*members)), true)... 
     }; 
} 

struct foo { 
    template <typename... T> 
    foo(int v, T... a): value(v), array{ a... } {} 
    int value; 
    int array[3]; 
    std::vector<int> vector; 
}; 

int main() { 
    queue q; 
    auto ptr = std::make_shared<foo>(1, 2, 3, 4); 
    enqueue(q, ptr, &foo::value, &foo::array, &foo::vector); 
    while (!q.empty()) { 
     q.front()(); 
     q.pop(); 
    } 
} 

在上面的代码q仅仅是一个简单的std::queue<std::function<void()>>但我希望你可以想象它可能是一个线程池将处理卸载到另一个线程。实际预定的处理也是微不足道的,但我希望你可以想象它实际上是一些大量的工作。

+1

也许我在这里错过了你的例子的一部分,但我不明白为什么'shared_ptr '是这个例子所必需的。这个例子同样适用于'shared_ptr '和'shared_ptr '(假设后者为'std :: experimental :: shared_ptr'),唯一的其他修改是让'f-> array'衰减,而不是它的地址和'&'。看到int(*)[42]'必须衰减到'element_type *'(也就是'int *')来匹配函数调用,但仍然没有明显的好处... –

+0

@ monkey_05_06:for具体的代码很容易丢弃操作符的地址。在处理数组的泛型代码中将是一个特殊的情况,它可以(而且很容易)避免。 –

+0

您能否展示一个通用代码使得它更简单的地方删除操作符的例子?数组衰减是隐含的,所以我想不出任何区分'T(*)[N]'和'T *'的区别是有用的。在这种情况下,'N'明确地不**存储在任何地方,即使在'T [N]'特化中,所以你甚至不能让'N'被转发给某个'size'参数(因为没有)。 –

相关问题