54

作为这个问题的延伸更快吗const_iterators,我还有一个问题const_iterators。如何删除 a 的常量const_iterator?尽管迭代器是指针的广义形式,但仍然const_iteratoriterators 是两个不同的东西。因此,我相信,我也不能使用const_cast<>to 转换 from const_iteratorto iterators。

一种方法可能是您定义一个迭代器,它将元素移动到哪个const_iterator点。但这看起来是一个线性时间算法。

关于实现这一目标的最佳方法的任何想法?

4

10 回答 10

85

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

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

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

向 Howard Hinnant 和 Jon Kalb 致敬这一技巧。

于 2012-05-19T21:40:06.980 回答
15

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

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

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

于 2009-04-19T10:00:50.780 回答
5

在您上一篇文章的答案中,有几个人(包括我在内)建议使用 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
};
于 2009-04-19T12:49:08.333 回答
4

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

于 2009-04-19T13:26:07.933 回答
2

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

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

像这样的东西

const_cast<T&>(*it);

于 2009-04-19T10:36:15.530 回答
2

我相信在精心设计的程序中不需要这种转换。

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

作为解决方法,您可以使用以下方法:

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 ) );

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

于 2009-04-19T11:38:21.483 回答
1

您可以从 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;

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

于 2009-04-19T10:00:12.280 回答
0

您可以将 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;
于 2009-04-19T10:37:35.823 回答
0

我认为想出一个适用于不在标准库中且不包含 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
于 2015-01-15T16:05:49.040 回答
0

假设您的容器const_iterator具有与其相同的布局iterator(所有 STL 容器的有效假设),您可以简单地将前者位转换为后者:

#include <bit>
#include <vector>

void demo() {
    using vec_t = std::vector<int>;
    vec_t v { 1, 2, 3 };
    vec_t::const_iterator c_it = v.cbegin();
    vec_t::iterator it = std::bit_cast<vec_t::iterator>(c_it);
    *it = 4; // OK, now v holds { 4, 2, 3 }
}
于 2022-03-02T02:11:10.333 回答