首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用unique_ptr实现的列表的Iterator

使用unique_ptr实现的列表的Iterator
EN

Stack Overflow用户
提问于 2016-09-01 17:00:30
回答 2查看 2.1K关注 0票数 0

我正在创建一个使用unique_ptr的数据结构。现在我想在这个数据结构上定义不同的迭代器,但是我的数据结构的节点是数据本身的一部分。因此,我希望迭代器返回实际的节点,而不仅仅是返回包含在其中的值。

以下是我到目前为止取得的成果(非常简单的例子):

代码语言:javascript
复制
#include <algorithm>
#include <iostream>
#include <memory>

using namespace std;

template <typename T> struct node {
  node(T val) : val(val), next(nullptr) {}
  node(T val, unique_ptr<node<T>> &n) : val(val), next(move(n)) {}
  T val;
  unique_ptr<node<T>> next;

  template <bool Const = true> struct iter {
    using reference =
        typename std::conditional<Const, const node<T> *, node<T> *>::type;
    iter() : nptr(nullptr) {}
    iter(node<T> *n) : nptr(n) {}

    reference operator*() { return nptr; }

    iter &operator++() {
      nptr = nptr->next.get();
      return *this;
    }

    friend bool operator==(const iter &lhs, const iter &rhs) {
      return lhs.nptr == rhs.nptr;
    }

    friend bool operator!=(const iter &lhs, const iter &rhs) {
      return lhs.nptr != rhs.nptr;
    }
    node<T> *nptr;
  };

  iter<> begin() const { return iter<>(this); }
  iter<> end() const { return iter<>(); }

  iter<false> begin() { return iter<false>(this); }
  iter<false> end() { return iter<false>(); }
};

template <typename T> void pretty_print(const unique_ptr<node<T>> &l) {
  auto it = l->begin();
  while (it != l->end()) {
    auto elem = *it;
    cout << elem->val << endl;
    ++it;
  }
}

int main() {
  auto a = make_unique<node<int>>(4);
  auto b = make_unique<node<int>>(3, a);
  auto c = make_unique<node<int>>(2, b);
  auto d = make_unique<node<int>>(1, c);

  for (auto *elem : *d) {
    elem->val = elem->val - 1;
  }

  pretty_print(d);

  return 0;
}

以这种方式公开数据结构元素的原始指针是否被认为是错误的做法?这是否适用于一个更复杂的例子,特别是对于const-correctness?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-09-01 17:11:04

这在很大程度上是种观点,但我认为这是个坏主意。unique_ptr应该是唯一的;如果您需要将一个原始指针传递给其他一些没有正确模板的函数(并且您知道它没有保留指针),那么就应该是罕见的例外。

否则,您将遇到一种情况,即合理的使用(例如使用迭代器初始化std::vector<node<int>*> )违反了unique_ptr中的假设。通常,您希望您的API能够以有限的开发人员头疼的方式运行,而且您的建议还增加了“只要您不将任何东西存储到超过我的自定义结构的生命周期之外的任何东西中,就可以迭代它”。

更好的选择是:

  1. 返回对实际unique_ptr的引用(这样,人们就可以在不违反唯一性契约的情况下与它们一起工作;如果不应该发生变异,则使它们成为const );他们将不得不承担所有权或显式使用“借来的”非托管指针来存储结果,但是迭代可以正常工作,并且不会意外地违反唯一性保证。
  2. 在内部存储shared_ptr,并在迭代期间分发新的shared_ptr,这样只要保留一个共享指针,所有权就会自动共享并延长生存期。

重要的是,这两种选择都不允许某人意外地“做错事”。调用者可以做不好的事情,但他们必须亲自明确地绕过智能指针保护机制才能做到这一点,这在他们的头上。

当然,第三种选择是:

  1. 返回指向的值的引用,而不是指针;如果值被存储,则应该复制构造。

用户可能会犯最后一个错误(通过长期存储引用,获取它的地址以获得违反唯一性保证的新指针,等等),但是对于std::vector这样的容器遵循现有的C++约定(正如Ryan在注释中指出的那样),所以这不是一个新的问题;C++开发人员通常都知道不要用迭代获得的引用做糟糕的事情。可以说是最好的选择,因为它很好地映射到标准模式,使开发人员更容易适应现有的心智模型。

票数 3
EN

Stack Overflow用户

发布于 2016-09-01 18:44:00

我将在迭代器中使用引用而不是指针。

代码语言:javascript
复制
using reference =
    typename std::conditional<Const, const node<T>&, node<T>&>::type;

所以迭代器只需要取消一次。(不过,我会为该成员保留指针)。

(公共)接口中的指针引发了对所有权的怀疑。

用法语法如下:

代码语言:javascript
复制
for (auto& elem : *d) {
  elem.val = elem.val - 1;
}

代码语言:javascript
复制
while (it != l->end()) {
   const auto& elem = *it;
   std::cout << elem.val << std::endl;
   ++it;
}

当迭代器的相应节点被释放时,迭代器确实会失效,这是常见的情况。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39277153

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档