我在Stroustrup的一本书中找到了这段代码:
void print_book(const vector<Entry>& book)
{
for (const auto& x : book) // for "auto" see §1.5
cout << x << '\n';
}
但const
似乎是多余的,因为x
将被推断为const_iterator
,因为参数中的book
为const
。 const auto
真的更好吗?
答案 0 :(得分:71)
在我看来,它看起来是显式的,这就是更好的原因。因此,如果我的意思是const
,那么我更愿意明确地写出来。它增强了本地推理,从而帮助其他人相对容易地理解代码 - 其他程序员不必查看book
的声明并推断出{{1} }}也是x
。
答案 1 :(得分:34)
代码不仅仅是编译器可以读取的 - 它也可供人类阅读。只有在不以可读性为代价的情况下,简洁才是好的。
在给定的代码中,推断constness是立即的。根据您提议的更改,推断常量将要求读者准确记住或查看book
的定义。
如果读者想要了解常数,那么简洁就会适得其反。
答案 2 :(得分:14)
更清楚地看到关键字const
。您立即意识到它不会被修改,如果const
关键字不存在,除非我们查看函数签名,否则我们不会知道。
明确地使用const
关键字对于代码的读者来说要好得多。
此外,当您阅读下面的代码时,您希望x
可以修改
for (auto& x : book) { ... }
然后当您尝试修改x
时,您会发现它会导致错误,因为book
是const
。
在我看来,这不是明确的代码。最好在代码中声明x
不将被修改,以便其他读取代码的人将期望正确。
答案 3 :(得分:7)
我将在这里采取不同的角度。 const
和const_iterator
意味着完全不同的事情。
const
意味着你不能修改迭代器本身,但是你可以修改它所指向的元素,就像声明为int* const
的指针一样。 const_iterator
表示你无法修改它所指向的元素,但你仍然可以修改迭代器本身(即你可以递增和递减它),就像声明为const int*
的指针一样。
std::vector<int> container = {1, 2, 3, 4, 5};
const std::vector<int> const_container = {6, 7, 8, 9, 0};
auto it1 = container.begin();
const auto it2 = container.begin();
auto it3 = const_container.begin();
const auto it4 = const_container.begin();
*it1 = 10; //legal
*it2 = 11; //legal
*it3 = 12; //illegal, won't compile
*it4 = 13; //illegal, won't compile
it1++; //legal
it2++; //illegal, won't compile
it3++; //legal
it4++; //illegal, won't compile
正如您所看到的,修改迭代器指向的元素的能力仅取决于它是iterator
还是const_iterator
,并且修改迭代器本身的能力仅取决于关于迭代器的变量声明是否具有const
限定符。
编辑:我刚刚意识到这是一个范围,因此在这里根本没有迭代器(至少没有明确地)。 x
不是迭代器,实际上是对容器元素的直接引用。但是你有正确的想法,无论是否明确写出,都会推导出const
限定符。
答案 4 :(得分:4)
我想提出一个反点。在Herb Sutter的2014年CppCon演讲中"Back to the Basics! Essentials of Modern C++ Style"(时间戳34:50),他展示了这个例子:
void f( const vector<int>& v) {
vector<int>::iterator i = v.begin(); // error
vector<int>::const_iterator i = v.begin(); // ok + extra thinking
auto i = v.begin(); // ok default
他认为即使你更改了函数的参数,auto
也会正常工作,因为它正确推导出const
或non-const
。在幻灯片36上,他指出&#34;你应该知道你的变量是不是const / volatile!&#34;为什么&#34; auto&amp;&amp;&#34;对局部变量不好。
基本上,它归结为意见问题。有些人认为明确是有利于可读性或可维护性的,但反过来可能会有争议:冗余表明缺乏理解(C ++规则或你的代码实际上正在做什么 1 )并且可能会造成伤害可读性和可维护性。做你认为最好的事情。
1 :这是一个相当人为的例子,但是C ++的拇指规则在一般情况下并不常用,很难记住。例如,Herb Sutter建议您使用与常规函数重载相反的值传递构造函数参数。这是const
到处都可以咬你的脚的情况之一,这取决于你是否同意Herb。
答案 5 :(得分:2)
尽早提及const
,并经常告诉读者更多关于的内容,而不会读者不得不在代码中进一步查看。
现在,根据您的代码,我将其写为:
void print_book(const std::vector<Entry>& book)
{
for (auto&& x : book)
std::cout << x << '\n';
}
甚至:
void print_book(std::experimental::array_view<const Entity> book)
{
for (auto&& x : book)
std::cout << x << '\n';
}
但只是因为代码太短,我会省略冗余的const
。
(array_view
版本删除了对vector
参数的无用依赖 - vector<?> const&
通常超过指定您的界面,因为vector<?> const&
没有提供任何有用的参数array_view<?>
没有提供,但可以从初始化列表或原始C数组等强制复制。)
就编译器而言,没有理由使用const auto&
而不是auto&
或auto&&
,因为所有3(在此上下文中)都被推断为相同的类型。使用其中一个的唯一原因是与其他程序员交谈。
在我的情况下,我默认使用auto&&
(它表示我正在迭代,而不是非常关心我正在迭代的内容)。
auto&
如果我知道我需要修改,auto const&
如果我想强调我不修改。
答案 6 :(得分:1)
这个具体的例子非常简单,因为输入数组被声明为const
。通常,在基于范围的for中使用const
对于开发人员都是有用的(如果他试图修改容器,他会遇到编译器错误)和编译器(显式const
有时帮助它优化代码)