cppreference.com 给出的std::set
容器的描述在末尾包含以下注释:
成员类型
iterator
和const_iterator
可以是同一类型的别名。由于iterator
可以转换为const_iterator
,因此const_iterator
应该在函数参数列表中使用,以避免违反一个定义规则。
我不明白最后这句话。我的理解是,一个集合不允许修改它的元素(如果你需要更改一个,你必须erase
它,然后insert
新的),所以每个iterator
都像一个const_iterator
。该标准补充说,它们可能是(但不是必需的)是同一类型。到目前为止很清楚。
我没有得到的是可能违反一个定义规则。该规则表示,一个函数可以有多个声明,但只有一个定义。这是怎么违反的?假设我有一个set<int>
,我创建了一个函数,该函数将迭代器作为参数。由于它们的工作方式相同,因此我可以选择其类型:set<int>::iterator
或set<int>::const_iterator
。如果我不听从建议,也就是说,我选择set<int>::iterator
会怎样?
我写了一个程序来试图找到答案。基本上有 2 个函数,一个接受iterator
,另一个接受const_iterator
,我调用它们中的每一个两次,一次通过iterator
,一次传递const_iterator
。在这里:
#include <iostream>
#include <set>
void print_set_iter(std::set<int>::iterator& it) {
std::cout << "Set element from iterator: " << *it << "n";
}
void print_set_const_iter(std::set<int>::const_iterator& const_it) {
std::cout << "Set element from const_iterator: " << *const_it << "n";
}
int main() {
std::set<int> primes = {2, 3, 5, 7, 11};
std::set<int>::iterator it = primes.find(3);
std::set<int>::const_iterator const_it = primes.find(5);
print_set_iter(it);
print_set_iter(const_it);
print_set_const_iter(it);
print_set_const_iter(const_it);
}
我已经使用 3 个最流行的编译器(gcc、clang、MSVC)在 rextester.com 上编译了这个:没有警告,它运行良好。通常我希望print_set_iter(const_it);
会导致错误,但事实并非如此。这是否意味着这 3 个实现对两个迭代器使用相同的类型? 但即使在这种情况下,即使我发现编译器对这些迭代器不使用相同的类型,我仍然不明白为什么会有 ODR 违规。如果类型不同,则禁止的转换(从 const 到 non-const)应该会触发错误,但这与 ODR 无关。谁能给我举个违规的例子,或者解释一下那条说明是什么意思?
您必须以相同的方式命名函数才能获得错误。更改的代码:https://rextester.com/SSNZ54459
错误是
source_file.cpp: In function ‘void print_set_iter(std::set<int>::const_iterator&)’:
source_file.cpp:8:6: error: redefinition of ‘void print_set_iter(std::set<int>::const_iterator&)’
void print_set_iter(std::set<int>::const_iterator& const_it) {
^
source_file.cpp:4:6: note: ‘void print_set_iter(std::set<int>::iterator&)’ previously defined here
void print_set_iter(std::set<int>::iterator& it) {
^
有两种可能:
类型- 别名可能引用也可能不引用同一类型;
- ODR 违规不需要诊断,因此您无法通过(甚至大量)示例来反驳它的存在。
如果别名类型相同,则属于 ODR 冲突,如下所示:
using type_1 = int;
using type_2 = int;
void func(type_1) {}
void func(type_2) {}
别名类型在签名中考虑,而不是可以为每种类型创建的任意数量的别名。上述两个定义的签名是相同的。