前提条件
假设我有一个容器类Box
,它提供了内部类const_iterator
和iterator
。因为我希望iterator
可以转换为const_iterator
,所以后者继承自前者:
class Box {
// ...
public:
class const_iterator : public std::iterator<std::random_access_iterator_tag, const int> { /* ... */ };
class iterator : public const_iterator { /* ... */ };
// ...
};
问题
现在我想使用Google Test来测试这些类。让我们断言begin()
和end()
不返回相同的东西:
const Box a;
EXPECT_NE(a.begin(), a.end());
遇到编译错误:
- clang:
Box::const_iterator中没有名为'begin'的成员
- g++:
'const class Box::const_iterator'没有名为'begin'的成员
原因
查阅资料后,我找到了Google Test源代码中的这个模板(点击链接获取详细文档)。
typedef int IsContainer;
template <class C>
IsContainer IsContainerTest(int /* dummy */,
typename C::iterator* /* it */ = NULL,
typename C::const_iterator* /* const_it */ = NULL) {
return 0;
}
这个模板的神奇之处在于,如果
EXPECT_*
的参数具有iterator
和const_iterator
成员类,则假定该类型是一个容器类。Google Test可以利用这一点,在期望失败时打印出相当人性化的报告,这很好。但是,还有一个小细节:// Note that we look for both C::iterator and C::const_iterator. The
// reason is that C++ injects the name of a class as a member of the
// class itself (e.g. you can refer to class iterator as either
// 'iterator' or 'iterator::iterator'). If we look for C::iterator
// only, for example, we would mistakenly think that a class named
// iterator is an STL container.
如果我理解正确的话,这意味着:
- `Box::const_iterator` 作为一个成员类被命名为 `const_iterator`,而 `std::iterator` 作为一个成员类被命名为 `iterator`。 - `Box::iterator` 作为一个成员类被命名为 `iterator`,而 `Box::const_iterator` 作为一个成员类被命名为 `const_iterator`。
因此,我的两个迭代器类都看起来像容器类,对于 Google Test 来说!
问题是:如何设计我的迭代器类,使它们不再看起来像容器?
我尝试过的方法有:
- 将 `const_iterator` 的 `std::iterator` 超类声明为 `private`。这样可以通过隐藏 `iterator` 成员类来解决 `const_iterator` 的问题,但是除非 `a` 是 `const`,否则它仍然不能让我将 `a.begin()` 作为参数传递给 `EXPECT_NE`。看起来 Google Test 使用的是 `iterator begin()` 而不是 `const_iterator begin() const`,原因不明。 - 完全删除 `std::iterator` 超类。这是一个坏主意吗?我想我必须手动声明我的 `std::iterator_traits`,还有其他我不扩展 `std::iterator` 就会失去的东西吗? - 将 `Box::const_iterator` 超类声明为 `private`。这可能是一个选项,因为我需要重新声明我宁愿重用的方法(如 `operator++`)。
还有其他我忽略的东西吗?
#include<iterator>
#include <memory> //unique_ptr<T>
#include <gtest/gtest.h>
class ThreeInts {
std::unique_ptr<int[]> v;
public:
ThreeInts() : v(new int[3]) { v[0] = 0; v[1] = 1; v[2] = 2; };
ThreeInts(int val) : ThreeInts() { v[0] = val; v[1] = val; v[2] = val; };
bool operator==(const ThreeInts& other) const {
return v[0] == other.v[0] && v[1] == other.v[1] && v[2] == other.v[2];
}
class const_iterator : public std::iterator<std::random_access_iterator_tag, const int> {
protected:
int* p;
public:
explicit const_iterator(int* p) : p(p) {}
const_iterator& operator++() { ++p; return *this; }
bool operator==(const const_iterator& rhs) const { return p == rhs.p; }
bool operator!=(const const_iterator& rhs) const { return p != rhs.p; }
int operator*() const { return *p; }
};
class iterator : public const_iterator {
public:
explicit iterator(int* p) : const_iterator(p) {}
int& operator*() const { return *p; }
};
iterator begin() { return iterator(v.get()); }
iterator end() { return iterator(v.get()+3); }
const_iterator begin() const { return const_iterator(v.get()); }
const_iterator end() const { return const_iterator(v.get()+3); }
};
TEST(ThreeInts, ThisTestCompilesAndPrettyFailureMessagesAreShown) {
const ThreeInts a(1), b(2);
ThreeInts c(1), d(2);
EXPECT_EQ(a, b);
EXPECT_EQ(a, c);
EXPECT_EQ(c, d);
}
TEST(ThreeInts, ThisTestCompilesIfTheStdIteratorParentIsPrivate) {
const ThreeInts a;
EXPECT_NE(a.begin(), a.end());
}
TEST(ThreeInts, ThisTestAlsoCompilesIfTheStdIteratorParentIsPrivateButItIsAHassle) {
ThreeInts a;
ThreeInts::const_iterator beg = a.begin();
ThreeInts::const_iterator end = a.end();
//EXPECT_NE(beg, end); // Compile error unless the std::iterator superclass is private
}
TEST(ThreeInts, ThisTestDoesNotCompileEvenIfTheStdIteratorParentIsPrivate) {
ThreeInts a;
//EXPECT_NE(a.begin(), a.end());
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
ThreeIntsIterator
或其他名称,但不要使用iterator
。在ThreeInts
中,执行typedef ThreeIntsIterator iterator;
。 - Igor TandetnikIsContainerTest
是垃圾 - 简单的typedef不构成容器 - 更好的方法是测试成员函数begin()
和end()
,但这种方法也有缺点。 - user2249683C::iterator
和C::const_iterator
,因为如果只查找前者,那么C
可能只是一个具有子类型iterator
而不是容器。 - WalterEXPECT_NE
不应该在没有证明其存在的情况下调用其参数的begin()
或end()
函数。即使是这样,也值得怀疑。这种模板魔法是耗时微妙的错误的绝佳来源 - Google的人们应该意识到他们的代码试图做的荒谬之处。 - cmaster - reinstate monicaiterator
继承const_iterator
方面存在一些问题。例如考虑operator++
,你想要重用它,但是它返回什么?是的,在两种情况下都返回const_iterator&
。这是不正确的,并且可能会破坏标准算法。对于你想要实现的+
和-
也是如此。最好分别实现两个迭代器类(可能使用CRTP从一个共同的父类继承),并提供转换运算符。 - n. m.