【问题标题】:How to remove constness of const_iterator?如何删除 const_iterator 的常量?
【发布时间】:2010-10-20 09:24:23
【问题描述】:

作为这个问题Are const_iterators faster? 的扩展,我还有一个关于const_iterators 的问题。如何删除const_iterator 的常量? 尽管迭代器是指针的广义形式,但 const_iteratoriterators 仍然是两个不同的东西。因此,我相信,我也不能使用const_cast<>const_iterator 转换为iterators。

一种方法可能是定义一个迭代器,它移动'直到const_iterator 指向的元素。但这看起来是一个线性时间算法。

您知道实现这一目标的最佳方法是什么吗?

【问题讨论】:

  • 你在使用 boost::multi_index 吗?

标签: c++ stl iterator const-iterator


【解决方案1】:

在 C++11 中有一个恒定时间复杂度的解决方案:对于任何序列、关联或无序关联容器(包括所有标准库容器),您都可以调用具有空范围的范围擦除成员函数:

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

范围擦除成员函数有一对const_iterator 参数,但它们返回一个iterator。因为提供了一个空范围,所以对擦除的调用不会更改容器的内容。

Hat tip to Howard Hinnant and Jon Kalb for this trick.

【讨论】:

  • 不过,您需要访问容器。
  • @xeo:当然。如果您可以在没有对容器的非常量引用的情况下执行此操作,那将是 const 安全性的一个巨大漏洞。
  • +1。 Ultra-pedantry:这适用于所有标准容器,因为所有标准容器都是序列或关联容器或无序关联容器。但erase 实际上并不是容器要求的一部分,因此它不一定适用于满足容器要求的所有用户定义类型。您已经在答案中说过这一点,但是将“无序关联”添加到括号中的列表中。也许这种迂腐应该适用于您对 Visage 答案的评论,您在其中说“所有容器”,而不是您的完整答案。
  • @SteveJessop:好点。我添加了无序的关联容器;我忘记了它们并不是真正的“关联容器”。
  • 应该注意,erase 调用暗示了某些容器​​的潜在迭代器和引用失效。当然,空范围不应该发生这种情况,但是某些 b̶r̶a̶i̶n̶-d̶e̶a̶d̶ 实现(例如 VS2017 的实现)可能会触发断言错误。
【解决方案2】:

不幸的是,线性时间是唯一的方法:

iter i(d.begin());
advance (i,distance<ConstIter>(i,ci));

其中 iter 和 constIter 是合适的 typedef,d 是您正在迭代的容器。

【讨论】:

  • 允许实现(并且确实)专门化 std::advance 和 std::distance 用于随机访问迭代器,这样对于某些容器来说这可以是恒定的时间。
  • 实际上,对于(良好实现的)随机访问迭代器来说,这应该是恒定的时间。见aristeia.com/Papers/CUJ_June_2001.pdf
  • 对于非随机访问迭代器,我认为iter i(d.begin()); while (ConstIter(i) != ci) ++i; 会更有效。仍然令人失望,但至少它只从i 向前走一次。您可以使用 iterator-type-tag 分派来编写函数模板,这些模板实际上对迭代器类型进行了重载,至少它们会假设迭代器被正确标记。
  • 有一个恒定时间的解决方案,它具有明确定义的行为并且适用于所有标准库容器(以及大多数其他容器);查看我刚刚发布的答案。
  • @JonathanJansson C++03 允许您所说的行为,但 C++11 (21.4.1#6) implicitly prohibits it。在 C++03 中明确允许 begin() 在某些情况下使迭代器无效的措辞已被删除,因此在 C++11 中 begin() 不再使迭代器无效。
【解决方案3】:

在您上一篇文章的答案中,有几个人(包括我)建议使用 const_iterators 而不是性能相关的原因。可读性,从设计板到代码的可追溯性......使用 const_iterators 提供对非常量元素的变异访问比根本不使用 const_iterators 要糟糕得多。你正在将你的代码转换成只有你能理解的东西,设计更差,可维护性也很痛苦。使用 const 只是为了抛弃它比根本不使用 const 要糟糕得多。

如果你确定你想要它,C++ 的好/坏部分是你总是可以得到足够的绳子来吊死自己。如果您打算使用 const_iterator 来解决性能问题,那么您真的应该重新考虑它,但如果您仍然想大吃一惊……那么 C++ 可以提​​供您选择的武器。

首先,最简单的:如果您的操作将参数作为 const(即使在内部应用 const_cast)我相信它应该在大多数实现中直接工作(即使它可能是未定义的行为)。

如果您无法更改函子,那么您可以从任一方面解决问题:在 const 迭代器周围提供一个非常量迭代器包装器,或者在非常量函子周围提供一个 const 函子包装器。

迭代器外观,漫漫长路:

template <typename T>
struct remove_const
{
    typedef T type;
};
template <typename T>
struct remove_const<const T>
{
    typedef T type;
};

template <typename T>
class unconst_iterator_type
{
    public:
        typedef std::forward_iterator_tag iterator_category;
        typedef typename remove_const<
                typename std::iterator_traits<T>::value_type
            >::type value_type;
        typedef value_type* pointer;
        typedef value_type& reference;

        unconst_iterator_type( T it )
            : it_( it ) {} // allow implicit conversions
        unconst_iterator_type& operator++() {
            ++it_;
            return *this;
        }
        value_type& operator*() {
            return const_cast<value_type&>( *it_ );
        }
        pointer operator->() {
            return const_cast<pointer>( &(*it_) );
        }
        friend bool operator==( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return lhs.it_ == rhs.it_;
        }
        friend bool operator!=( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return !( lhs == rhs );
        }
    private:
        T it_;  // internal (const) iterator
};

【讨论】:

    【解决方案4】:

    Scott Meyer's article 选择迭代器而不是 const_iterators 回答了这个问题。 Visage 的答案是唯一安全的 C++11 之前的替代方案,但实际上对于实现良好的随机访问迭代器来说是恒定时间,而对于其他迭代器来说是线性时间。

    【讨论】:

    • 这篇文章是 2003 年之前的标准(从 2001 年开始)。我希望看到 2003 年标准更改后的更新版本
    • @DavidRodríguez-dribeas:请参阅我的答案,了解 C++11 的定义明确的恒定时间复杂度解决方案(晚了三年,但总比没有好!:-D)。
    【解决方案5】:

    这可能不是您想要的答案,但有些相关。

    我假设你想改变迭代器指向的东西。我做的最简单的方法是 const_cast 代替返回的引用。

    类似的东西

    const_cast&lt;T&amp;&gt;(*it);

    【讨论】:

    • 某些函数,如擦除等。需要一个 const_iterator,所以这不起作用。
    • 你的意思是erase需要一个非常量的迭代器,对吧?如果是这样,你为什么首先使用 const_iterator ?大多数时候,我需要的这种 const cast 是为了调试属性。
    【解决方案6】:

    我相信设计良好的程序不需要这种转换。

    如果您需要这样做 - 尝试重新设计代码。

    您可以使用以下解决方法:

    typedef std::vector< size_t > container_type;
    container_type v;
    // filling container code 
    container_type::const_iterator ci = v.begin() + 3; // set some value 
    container_type::iterator i = v.begin();
    std::advance( i, std::distance< container_type::const_iterator >( v.begin(), ci ) );
    

    但我认为有时这种转换是不可能的,因为您的算法无法访问容器。

    【讨论】:

    • +1 重构。此外,当使用 const_iterators 时,它的目的是为了提高性能。
    【解决方案7】:

    您可以从 const_iterator 中减去 begin() 迭代器以获得 const_iterator 指向的位置,然后将 begin() 加回到该位置以获得非常量迭代器。我不认为这对于非线性容器会非常有效,但对于向量等线性容器,这将需要恒定的时间。

    vector<int> v;                                                                                                         
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    vector<int>::const_iterator ci = v.begin() + 2;
    cout << *ci << endl;
    vector<int>::iterator it = v.begin() + (ci - v.begin());
    cout << *it << endl;
    *it = 20;
    cout << *ci << endl;
    

    编辑:这似乎只适用于线性(随机访问)容器。

    【讨论】:

    • 只有当你定义了一个合适的运算符来从 const 迭代器中减去迭代器时,它才会起作用。 AFAIK 没有这样的事情。
    • 它可能适用于向量(随机访问迭代器)。它可能不适用于列表和其他容器。
    • @Visage:您不需要合适的运算符,在这种情况下,您从 const_iterator 中减去 const_iterator,获取整数偏移量,然后将其添加到迭代器中。完全有效,并且按预期工作。
    • 更具体地说,这仅适用于随机访问迭代器,因为它是定义必要操作的概念。查看 SGI 文档 (sgi.com/tech/stl/RandomAccessIterator.html) 了解我认为最好的描述。
    【解决方案8】:

    您可以将您的 const 迭代器值指针转换为非 const 值指针并直接使用它

        vector<int> v;                                                                                                         
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);
    v.push_back(2);
    vector<int>::const_iterator ci = v.begin() + 2;
    cout << *ci << endl;
    *const_cast<int*>(&(*ci)) = 7;
    cout << *ci << endl;
    

    【讨论】:

    • 这对std::vector 和其他具有连续存储的容器“有效”,但不适用于其他容器(如std::list)。
    【解决方案9】:

    我认为想出一个适用于不在标准库中且不包含 erase() 方法的容器的解决方案会很有趣。

    尝试使用它会导致 Visual Studio 2013 挂起编译。我不包括测试用例,因为将其留给能够快速找出界面的读者似乎是个好主意;我不知道为什么这会挂在编译上。即使 const_iterator 等于 begin() 也会发生这种情况。

    // deconst.h
    
    #ifndef _miscTools_deconst
    #define _miscTools_deconst
    
    #ifdef _WIN32 
        #include <Windows.h>
    #endif
    
    namespace miscTools
    {
        template < typename T >
        struct deconst
        {
    
            static inline typename T::iterator iterator ( typename T::const_iterator*&& target, T*&& subject )
            {
                typename T::iterator && resultant = subject->begin ( );
    
                bool goodItty = process < 0, T >::step ( std::move ( target ), std::move ( &resultant ), std::move ( subject ) );
    
            #ifdef _WIN32
                 // This is just my habit with test code, and would normally be replaced by an assert
                 if ( goodItty == false ) 
                 {
                      OutputDebugString ( "     ERROR: deconst::iterator call. Target iterator is not within the bounds of the subject container.\n" ) 
                 }
            #endif
                return std::move ( resultant );
            }
    
        private:
    
            template < std::size_t i, typename T >
            struct process
            {
                static inline bool step ( typename T::const_iterator*&& target, typename T::iterator*&& variant, T*&& subject )
                {
                    if ( ( static_cast <typename T::const_iterator> ( subject->begin () + i ) ) == *target )
                    {
                        ( *variant ) += i;
                        return true;
                    }
                    else
                    {
                        if ( ( *variant + i ) < subject->end () )
                        {
                            process < ( i + 1 ), T >::step ( std::move ( target ), std::move ( variant ), std::move ( subject ) );
                        }
                        else { return false; }
                    }
                }
            };
        };
    }
    
    #endif
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      • 1970-01-01
      相关资源
      最近更新 更多