【问题标题】:Temporary read-only copy of unique_ptrunique_ptr 的临时只读副本
【发布时间】:2013-04-08 19:46:07
【问题描述】:

我对 C++11 的智能指针非常陌生,我正在尝试在项目中有效地使用它们。在我的项目中,我有很多函数使用 const 引用 vectorunique_ptr,对其进行一些计算,并将一些结果放入返回参数中,如下所示:

void computeCoefficients(const vector<unique_ptr<Scalar>>& roots, 
    vector<unique_ptr<Scalar>>& coeffs) {
    ...
}

我正在使用unique_ptr,因为调用所有这些函数的过程是vector 中对象的唯一所有者,并且这些函数只是“借用”对象以便将它们作为输入读取。

现在我正在尝试编写一个函数,对它接收到的 vector 的不同子集进行计算,并且为了做到这一点,它需要具有包含这些子集的 vector 的不同“版本”传递给另一个以vector&lt;unique_ptr&lt;Scalar&gt;&gt; 作为输入的函数。但是获得向量子集的唯一方法是复制它——这是一个问题,因为unique_ptrs 不能被复制。我希望代码看起来像这样:

void computeOnSet(const vector<unique_ptr<Scalar>>& set, unique_ptr<Scalar>& output) {  
    ...
}

void computeOnAllSubsets(const vector<unique_ptr<Scalar>>& set, vector<unique_ptr<Scalar>>& outputs) {
    for(int i = 0; i < input.size(); i++) {
        auto subset = vector<unique_ptr<Scalar>>(set.begin(), set.begin()+i);
        subset.insert(subset.end(), set.begin()+i+1, set.end();
        computeOnSubset(subset, outputs.at(i));
    }
}

这当然行不通。如果我用shared_ptrs 替换unique_ptrs,我可以让它工作,但这有两个问题:

  • 从哲学上讲,这意味着我与computeOnSubsets 函数共享该集合的所有权,而我不是;调用者仍然是唯一的所有者。 (我读到shared_ptr 表示您正在与拥有它的副本的所有东西共享所有权)。
  • 它会引入引用计数的开销,即使在我不需要它的地方也是如此,因为它会迫使我将所有方法的输入参数更改为vector&lt;shared_ptr&lt;Scalar&gt;&gt;

我要做的就是制作一个 临时的、只读的指针副本,其唯一目的是制作 临时的、只读的子向量。有没有办法做到这一点? weak_ptr 听起来像我需要的(非拥有临时指针),但它只能与 shared_ptr 一起使用。

【问题讨论】:

  • 你确定不能只拥有vector&lt;Scalar&gt;吗?
  • 是的,因为Scalar实际上是一个接口,而不是一个具体的类。据我所知,对 STL 容器使用继承和多态性的唯一方法是使用指针,而不是值类型。
  • 是的,没错。我虽然 Scalar 是一个具体的、轻量级的类。
  • 你可以让你的函数接受迭代器(同时泛化),然后简单地传递转换迭代器来取消引用源迭代器(Boost 可能已经有了这个)。
  • 如果computeCoefficients 产生vector&lt;unique_ptr&lt;Scalar&gt;&gt;&amp; coeffs,为什么不返回它?使用返回值的参数列表是不好的风格。

标签: c++ c++11 shared-ptr stdvector unique-ptr


【解决方案1】:

我使用 unique_ptr 是因为调用所有这些函数的过程是向量中对象的唯一所有者,而这些函数只是“借用”对象以便将它们作为输入读取。

由于计算函数不拥有指向的对象,只是观察它们的状态并进行计算,你应该向它们传递一个观察指针的向量(在这种情况下,常规的原始指针),而不是 unique_ptrs 的向量。

由于computeOnAllSubsets()computeOnSet() 不对Scalar 对象的生命周期负责,它们甚至不应该获得它们的所有权——换句话说,它们不应该接收拥有的unique_ptrs。

毕竟,程序的逻辑可以保证这些函数不会接收悬空引用,因为拥有函数在执行所有必要的计算之前不会破坏它的向量。您正在编写的内容直接支持这一点:

我要做的只是制作一个临时的、只读的指针副本,其唯一目的是制作临时的、只读的子向量。有没有办法做到这一点?

只需将原始指针向量传递给您的计算函数。给定一个unique_ptr,你可以通过调用成员函数get()来访问封装的原始指针:

std::unique_ptr<Scalar> pS // ... initialized somehow
Scalar* pScalar = pS.get();

作为原始指针的替代方法,您可以使用std::reference_wrapper 来调度观察引用。特别是在重构遗留代码库的过程中,原始指针用于手动内存管理,这将清楚地表明引用对象的所有权属于其他地方。

但是请注意,在现代 C++ 中,原始指针通常是观察指针的同义词,因此上述区别在广义上下文中没有真正意义。 std::reference_wrapper 的基本用例是当您希望通过引用某个按值接受其参数的函数模板来传递对象时(std::bind() 是一个典型示例)。

【讨论】:

  • 引用呢,即reference wrappers?我尽量避免使用原始指针,因为(从类型上)不清楚所有权概念是什么。
  • @DyP:是的,这也是一种可能。我相信在这种情况下观察到的任何东西都是可以的,并且我个人只在观察时才考虑使用原始指针 - 所以所有权概念在这种假设下很清楚,即永远不应该使用原始指针作为拥有指针。所以恕我直言,引用包装器和原始指针同样好。
  • @DyP,原始指针的所有权很明确:您不拥有它。如果您拥有它,您将拥有一个智能指针。 reference_wrapper 用于通过衰减参数的函数传递东西,它不是“愚蠢的智能指针”
  • @JonathanWakely 我想到了this GotW,以及一些不喜欢智能指针的程序员/库的经验。在智能指针出现之前,只有原始指针,所以我看不到原始指针如何说明所有权 - 您可以在智能指针广泛使用的代码库中实现这个(不拥有)作为准则使用,我会提倡。很明显,您没有引用,但您不能将它们存储在容器中,因此 reference_wrappers.
  • @DyP:我想我将使用reference_wrappers,因为它们对所有权语义更加清晰,而且我正在做的项目涉及从使用原始代码的旧代码库升级代码到处都是指针(因此查看我的代码的人可能会认为原始指针仍被用作拥有指针)。如果您将其写为答案,我会接受...
【解决方案2】:

使用迭代器可能会使您的代码更加通用。只需声明您的计算函数,如下所示:

template <typename InputIt, typename OutputIt>
void compute(InputIt first_it, InputIt last_it, OutputIt d_first, Output d_last);

此计算函数模板处理[first_it, last_it)范围内的数据,然后将结果放入[d_first, d_last)。它不关心输入或输出容器的类型。迭代器充当容器中元素的指针,这就是 STL 的思想。

此外,在某些情况下,您甚至不需要手动检查范围。只需使用&lt;algorithm&gt; 中的函数模板,例如std::for_eachstd::transform

我不太了解computeOnAllSubsets 中的代码sn-p,但本能地我认为std::transform 可能会有所帮助。

【讨论】:

  • +1 一开始我以为unique_ptrs会有问题,但是如果你用const_iterators应该没问题。
  • 我不这么认为。 C++ 标准不限制 InputIterator Concept 应该是常量。所以,我猜,遵循标准中的声明可能更笼统。
  • 对于std::unique_ptr,顺便说一句,我同意你的看法。将std::unique_ptr 放入容器中并不是一个好主意,因为在使用诸如std::sort(std::begin(v), std::end(v), std::greator&lt;T&gt;()) 之类的STL 函数时可能会出现很多不可读的编译错误。这样做意味着失去 STL 功能。
  • Ad 1) 是的,但您不希望计算函数更改所有权。授予对非常量 unique_ptrs 的访问权限将允许这样做。广告 2)您需要一个比较函数来尊重指针,shared_ptr 或原始指针相同。将unique_ptrs 放入标准容器中是可能且有用的(这是他们替换auto_ptr 的原因之一)。
  • 1) 迭代器充当指针,但实际上不是真正的指针。它确实是一种价值类型。所以,一个 const 迭代器并不意味着它的指向元素是 const。 2)这取决于。也许 std::sort 永远不会被使用:)。我认为替换 auto_ptr 的原因是将 unique_ptr 放入容器中可能会出现编译错误,而 auto_ptr 的运行时错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-12
  • 2017-08-13
  • 2021-01-19
  • 1970-01-01
  • 2020-12-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多