【问题标题】:Const methods and custom iteratorsconst 方法和自定义迭代器
【发布时间】:2020-04-21 11:25:07
【问题描述】:

我有一个包含std::vector 的类,另一个是该类的自定义iterator。它适用于非常量方法,但是当我尝试在const 方法中创建iterator 时,编译器会失败,因为它无法将const_iterator 转换为iterator。例如,下面的代码失败了,但是如果对ConstFunc的调用被注释掉了,它编译并运行良好。

#include <vector>

template <class T>
class FooIter {
public:
    typename std::vector<T>::iterator iterator;

    FooIter(std::vector<T> &vec) {iterator = vec.begin();}
};

template <class T>
class Foo {
public:
    std::vector<T> vec;

    Foo() {}
    void NonConstFunc() {
        FooIter<T> iter(vec);
    }
    void ConstFunc() const {
        FooIter<T> iter(vec);
    }
};

int main()
{
    Foo<int> foo;
    foo.NonConstFunc();
    foo.ConstFunc();
    return 0;
}

有没有办法修改FooIter,使其可以同时用作iterator 和const_iterator?

编辑:一位评论者问“您打算如何在 const 方法中使用 FooIter&lt;T&gt;?”下面是一个例子。

    std::vector< std::pair<unsigned, unsigned> > find() const {
        std::vector< std::pair<unsigned, unsigned> > list(0);
        Matrix2dIterator<T> it = this->begin(true);
        Matrix2dIterator<T> itEnd = this->end(true);
        for (; it != itEnd; ++it) {
            if (*it) {
                list.push_back(std::make_pair(it.getRow(), it.getCol()));
            }
        }
        return list;
    }

Matrix2d 是“Foo”,Matrix2dIterator 是“FooIter”。 find 需要遍历保存矩阵数据的 std::vector,但它不会更改该数据。

【问题讨论】:

  • 您可能需要FooIter 和ConstFooIter,因为iterator 和const_iterator 是不同的类型。你可以用union/variant做一些奇怪的事情,但这会让你的代码难以阅读。
  • 根据我的经验,尝试使 iterator 和 const_iterator 类型相同是行不通的。
  • 您打算如何在const 方法中使用FooIter&lt;T&gt;?
  • 您可以尝试使用 FooIter 类的一些额外模板参数化,但通常只为 iterator 和 const_iterator 设置两个单独的类型会更容易。
  • @rustyx 我添加了一个示例,说明我打算如何在问题中使用 const_iterator。

标签: c++ oop templates iterator


【解决方案1】:

这样做的原因是,您可以通过std::vector&lt;T&gt;::iterator 修改vec 并因此修改您的班级。因此,您不能将它们放在 const 成员函数中。更技术性的原因是在const 成员函数中,所有成员都是const,而std::vector 具有功能

std::vector<T>::iterator begin();
std::vector<T>::const_iterator begin() const;
std::vector<T>::const_iterator cbegin() const;

所以如果vec 是const begin() 将返回一个const 迭代器,这也是非常明智的,因为你不能通过const_iterator 修改向量。

您可以通过提供具有std::vector&lt;T&gt;::const_iterator 类型成员的ConstFooIter 来调整您的代码,如下所示:

#include <vector>

template <class T>
class ConstFooIter {
public:
    typename std::vector<T>::const_iterator iterator;

    ConstFooIter(const std::vector<T> &vec) {iterator = vec.cbegin();}
};

或者,您不在向量的 value_type 上而是在向量本身上进行模板化,这会自动检测 const-ness:

#include <vector>
template <class T, class = std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::vector<typename T::value_type, typename T::allocator_type>>>>
class FooIter {
public:
    decltype(std::declval<T>().begin()) iterator;

    FooIter(T &vec) {iterator = vec.begin();}
};

template <class T>
class Foo {
public:
    std::vector<T> vec;

    Foo() {}
    void NonConstFunc() {
        FooIter iter(vec);
    }
    void ConstFunc() const {
        FooIter iter(vec);
    }
};

int main()
{
    Foo<int> foo;
    foo.NonConstFunc();
    foo.ConstFunc();
    return 0;
}

请注意,这并不意味着您只剩下一门课。您的两种方法中的iters 将属于不同的类型(FooIter&lt;std::vector&lt;T&gt;&gt; 与FooIter&lt;const std::vector&lt;T&gt;&gt;)。优点是编译器将为您完成编写这两个类的繁琐工作,因此您减少了代码重复,因为这两个类几乎相同。

这是不利的。现在使用哪个变体并不容易,并且在非const 上下文中使用const 迭代器(如果你不打算修改任何东西,你应该总是这样做)有点棘手,因为@987654343当vec 不是常量时,@ 将始终返回非const 变体。

编辑:添加了一些模板魔法,只允许std::vector,即std::set&lt;int&gt; s; FooIter f{s}; 将不再编译。

【讨论】:

  • 伙计,我不知道我对模板知之甚少。我什至不知道存在 std::enable_if_t 等。感谢您启发我并将其放在一起。
  • 在FooIter iter(vec); 行中,我应该为FooIter 使用什么模板特化?我知道它应该类似于FooIter&lt;T, ???&gt; iter(vec);,但我不知道用什么替换问号。
  • 只需使用FooIter&lt;std::vector&lt;T&gt;&gt; 或FooIter&lt;const std::vector&lt;T&gt;&gt;。第二个参数有一个默认值,不必(实际上不应该)提供。
  • @JimClay 模板元编程是 C++ 中一个有趣的兔子洞。^^
【解决方案2】:

您应该选择 n314159 的解决方案之一。我只是展示了让您的FooIter 同时适用于非常量和常量向量的问题。

您可以向FooIter 添加一个重载的构造函数,该构造函数采用 const 引用:

FooIter(const std::vector<T> &vec) {iterator = vec.begin();}

这已经足够了,如果不是因为iterator 本身是非常量的,那么编译器会理所当然地抱怨这一点。你可以有两个成员变量,一个非常量,另一个是常量:

typename std::vector<T>::iterator iterator;
typename std::vector<T>::const_iterator const_iterator;

FooIter(std::vector<T> &vec) {iterator = vec.begin();}
FooIter(const std::vector<T> &vec) {const_iterator = vec.begin();}

但是其他成员函数需要知道使用哪一个。您可以为此添加一个标志变量,但这将是相当低效的;它需要更多内存(除了迭代器本身的重复),并且需要检查每个成员函数调用。所以最好的解决方案是有两个独立的迭代器类,一个用于非 const,另一个用于 const。

【讨论】:

    【解决方案3】:

    也许..?

    #include <vector>
    using namespace std;
    
    
        template <class T>
        class FooIter {
        public:
            const typename vector<T>::iterator itr;         // const added
    
            FooIter(vector<T> &vec) : itr{vec.begin()} {}
        };
    
        template <class T>
        class Foo {
        public:
            vector<T> vec;
    
            Foo() {}
            void NonConstFunc() {
                    FooIter<T> iter(vec);
            }
            void ConstFunc()  {                 // const removed, if
                    FooIter<T> iter(vec);       // needed prefix it on each needed line
            }
        };
    
        int main()  {
            Foo<int> foo;
            foo.NonConstFunc();
            foo.ConstFunc();
            return 0;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-13
      • 2016-04-05
      • 1970-01-01
      • 2015-02-04
      相关资源
      最近更新 更多