【问题标题】:Why const_iterator does not provide a base like reverse_iterator?为什么 const_iterator 不提供类似 reverse_iterator 的基础?
【发布时间】:2016-01-29 10:09:30
【问题描述】:

为什么const_iterator 不提供const_iterator::base() 函数,而像reverse_iterator 那样获取对应的非常量iterator 呢?

考虑以下伪代码(例如几何算法):

std::container< point > universe;
auto it = std::cbegin(universe);
std::list< decltype(it) > interesting_subset = sieve(it, std::cend(universe));
auto structure = algorithm(interesting_subset);

其中universe 是所有输入点。在sieve()-ing 之后,interesting_subset 包含指向universe 成员子集的迭代器。在algorithm() 之后从interesting_subset 构造一个结果structure,其中包含对universe 成员的引用(迭代器)。

最后,我想将points 更改为结果structure(例如,移动它们)。但同样我想在algorithm 操作期间保护他们免受修改,因此我使用std::cbegin/std::cendstd::begin/std::end 相反。最后我只有const_iterator 对源points 的引用。

这是 iterator std::container&lt; T &gt;::const_iterator::base() const 成员函数的一个非常用例,我希望将其呈现到 STL 容器中。

【问题讨论】:

  • 如果底层容器是const怎么办?
  • @molbdnilo :) 有趣的问题。也许运行时错误(抛出异常)?或者可能应该有两个版本的const_iterator(比如说,当前应该替换为really_const_iterator =)。
  • @molbdnilo 也许std::cbegin(non_const_container) 应该返回具有成员函数base()const_iterator 的增强版本。
  • 如果container 支持随机访问迭代器,您可以使用auto offset = it - cbegin(universe); auto new_it = begin(universe) + offset; 轻松地将it 转换为非常量版本。如果不是随机访问,效率会低。
  • @BoPersson 是的。同样对于任何其他容器(如果它由完全不同的元素组成),我可以通过std::addressof(*cit) == std::addressof(*it) 比较找到对应的元素。但它会导致额外的二次复杂度步骤来找到所有对应的元素。

标签: c++ stl iterator containers const-iterator


【解决方案1】:

这是一个有趣的问题。您希望能够在推理时保护这些点,但在推理完成后返回对它们的可变引用。

由于对这些点进行推理,您实际上返回的是它们的“名称”或“标识符”。

可以想象,我们可以按名称映射它们,并让sieve() 返回相关名称的向量。如果我们想避免存储正式名称(唯一数字、文本字符串等)的开销,这样的名称可以只是一个地址。

如果我们使用 const 对象的地址作为名称,那么当然要将其转回对可变对象的引用,我们需要 const_cast。这可能被视为我们可以合理使用 const 强制转换的少数几次之一。这样做时,我们应该将其封装在实用程序类中以限制任何后果。

编辑:重新考虑解决方案。这个现在不能被不守规矩的客户端代码滥用。

#include <iostream>
#include <vector>

struct point { int x, y; };

inline std::ostream& operator<<(std::ostream& os, const point& p)
{
    os << "(" << p.x << ", " << p.y << " )";
    return os;
}

struct point_collection
{
    using container_type = std::vector<point>;

    point_collection(container_type points) : _points(std::move(points)) {}

    using const_iterator = const point*;
    using iterator = point*;

    const_iterator begin() const { return &*_points.begin(); }
    const_iterator end() const { return &*_points.end(); }

    // note that this function is only available on a non-const point_collection
    point& mutable_reference(const_iterator input)
    {
        // could put an assert in here to ensure that input is within our range
        return *const_cast<iterator>(input);
    }

    container_type _points;
};


std::vector<point_collection::const_iterator> sieve(point_collection::const_iterator first,
                                                    point_collection::const_iterator last)
{
    std::vector<point_collection::const_iterator> result;

    for ( ; first != last ; ++first )
    {
        if (first->x > 6)
            result.push_back(first);
    }
    return result;
}


point_collection make_a_universe()
{
    return {
        std::vector<point> {
            { 10, 10 },
            { 6, 6 }
        }
    };
}

auto main() -> int
{
    using namespace std;
    auto universe = make_a_universe();

    auto interesting = sieve(universe.begin(), universe.end());

    for (auto point_id : interesting) {
        auto& p = universe.mutable_reference(point_id);
        p.x = -p.x;
        cout << p << endl;
    }
    return 0;
}

预期输出:

(-10, 10 )

【讨论】:

  • const_cast 肯定是解决方法(但丑陋)。迭代器的包装器是非常好的解决方法(概念)。如果 STL 自己有实现就好了。
  • @Orient stl 已经有了map 的概念。 map 将键(名称)映射到对象(值)。在这种情况下我所做的只是走捷径,但原理是相似的。
  • 'std::map' 有内存成本和运行时成本,但做的事情微不足道。
  • 如果宇宙是由 unordered_map&lt;ident, point&gt; 建模的,那么你根本不需要迭代器——你可以只推理一组 idents。但是,您是对的,在这种特殊情况下,需要在逻辑正确性和性能之间进行权衡。
  • @orient 最后评论。更新了解决方案。这是完全 const 正确的,因为它只允许通过可变容器转换 const_ref。它现在是最佳的快速安全。
【解决方案2】:

reverse_iterator 不会改变底层对象是否为 const,并且 const_iterator 与迭代器无关(除了可转换和引用的要求),因此您是在比较苹果和橘子。

如果const_iterator 确实可以通过base 提供对非常量迭代器的访问权限,则可以执行类似的操作

auto const & a = std::vector<int>(20, 1);
auto it = std::cbegin(a);
*it.base() = 4;

标准允许这样做。

您确实-原则上-想要规避const_iterator提供的修改保护。但这就是const_iterator 的意义所在。所以这既不是一个好主意,也不太可能发生,我认为(并希望)。

尽管我认为这是XY Problem,但我还是回答了这个问题,而不是提供如何正确操作的指导。

-编辑-

如果您希望 const_iterator 能够返回 iterator,那么您需要的是 iterator

你的意图似乎是

  1. const_iterator 传递给sieve,其中sieve 不允许更改元素。
  2. 将完全相同的迭代器传递给 algorithm 允许它修改它们。

您需要来自同一个对象的两种不同的东西。 没有人阻止sieve 的实现者使用it.base(),因此根本无法保证sieve 不会更改元素。我再说一遍,这就是问题的重点const_iterator

如果有任何使用const_iterators 改变事物的方法,它只会破坏它们。

【讨论】:

  • 两种不同的(但可能兼容的)类型建模与const_iterator 相同的语义,仅因base() 成员函数的存在而不同? const_iterator std::begin(T const &amp;)desired_const_iterator std::begin(T &amp;) 可能重载。
  • WRT 成员函数。 std::containers 可以提供非 const 限定的成员函数,能够将 const_iterator 转换为 iterator
  • 这样写没问题:template&lt;class C&gt; typename C::iterator iter_deconst(typename C::const_iterator iter, C &amp;c) { return std::begin(c)+std::distance(std::cbegin(c), iter); }
  • 不支持随机访问迭代器的各种容器(std::list/std::forward_list 和所有关联容器)怎么办?即使我将std::next 用于前向迭代器,那么从整个算法的输出中,每个迭代器都会增加额外的线性复杂度。
  • 关于 --edit-- 之后的文字 - 你对问题的解释完全错误。
【解决方案3】:

出于同样的原因,没有从const T*T* 的转换:const-correctness。

reverse_iterator 的比较无效,因为“反向迭代器”和“正向迭代器”之间的区别与“常量迭代器”和“非常量迭代器”之间的区别完全正交。后者有后果;前者最终不会。

【讨论】:

  • 调用'base()'成员函数肯定是显式转换。
  • 在问题的上下文中很重要。
  • 在进一步讨论之前,请务必阅读我已经存在的所有 cmets 以获取其他答案。
  • 第二小段很明显。我提到 reverse_iterator 只是为了成员函数名借用。
  • @Orient 你最了解然后队友。
【解决方案4】:

为什么 const_iterator 不提供 const_iterator::base() 函数,像 reverse_iterator 那样获取对应的非常量迭代器?

维护常量安全。提供这样的功能是非常危险的,这里已经详细讨论过了。

最后,我想改变点,包含到结果结构中(比如,移动它们)。但同样我想在算法操作期间保护它们免受修改,因此我使用 std::cbegin/std::cend 与 std::begin/std::end 相反。最后,我只有 const_iterator 对源点的引用。

好吧,您对base-member 提出了错误的要求。当然它会解决你的问题,但正如所说,这太危险了。让我为你重新提出一个问题:

如果我有一个对象的const_iterator 和对容器的非常量访问,我如何有效地(在恒定时间内)获得一个iterator 到引用的对象?

这里有一个花哨的技巧来做到这一点:

template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
    return c.erase(it, it);
}

我不认为这个技巧有任何功劳。我是从 https://stackoverflow.com/a/10669041/2079303 找到的,他们归功于 Howard Hinnant and Jon Kalb

正如该答案的 cmets 中所讨论的,此技巧适用于所有标准容器,但不一定适用于所有可能的符合标准的第三方容器,因为它们不需要提供 erase

就我个人而言,我希望标准容器有一个非常量成员函数,可以将给定的 const_iterator 转换为 iterator,但它们没有,所以你需要解决它。

【讨论】:

    猜你喜欢
    • 2011-02-23
    • 1970-01-01
    • 1970-01-01
    • 2021-08-09
    • 1970-01-01
    • 2011-05-25
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    相关资源
    最近更新 更多