【问题标题】:Isn't this const auto& redundant?这不是 const auto& 多余的吗?
【发布时间】:2016-10-02 23:53:37
【问题描述】:

我在 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真的更好吗?

【问题讨论】:

  • 看到for (auto&amp; x : something)首先想到的是它正在修改元素。很高兴在阅读代码时能够让我的期望与现实保持一致。
  • 我会求助于养成习惯的论点:当你有一个好的和普遍的习惯时,编写好的代码会花费你更少的脑力。
  • @chris 不过有相反的问题。如果他们没有意识到book 是const,那么他们可能会认为x 是可修改的。
  • 在函数更改为接收非常量向量的情况下,它更加健壮。然后循环仍在为自己的目的做正确的事情。
  • "x 将被推断为const_iterator",没有x 将被推断为const Entry&amp;。

标签: c++ constants


【解决方案1】:

在我看来,它看起来显式,这就是它更好的原因。所以如果我的意思是const,那么我更愿意明确地写出来。它增强了局部推理,从而帮助其他人比较容易地理解代码——其他程序员不必看book的声明并推断x是@ 987654324@也一样。

【讨论】:

  • 另外,在for 中包含const 表示循环不会修改x,即使book 恰好不是const。
【解决方案2】:

这个具体的例子很简单,因为输入数组被声明为const。一般来说,在基于范围的 for 中使用 const 对开发人员(如果他尝试修改容器会得到编译器错误)和编译器(显式 const 有时有助于优化代码)都有用

【讨论】:

  • 1) “如果他尝试修改容器会得到编译器错误”。即使开发人员没有明确提及但推断为const,开发人员也会收到错误消息。 2)“explicit const 有时会帮助它优化代码”..我对此表示严重怀疑,因为编译器无论如何知道它;明确提及const 对编译器没有任何帮助。
  • @Nawaz, 1) std::vector&lt;int&gt; v = {1,2,3,4}; for (auto &amp;i : v) i++; 是完全合法的。正如我所说,总的来说。 2)假设两个外部函数:一个是const int &amp;,第二个是int &amp;。因此,int i = 5; func(i); 可以优化为在第一种情况下直接传递5,但不能在第二种情况下。
  • 安德烈,请不要断章取义。本主题是关于“const”,这对于 OP 来说似乎是多余的,这意味着如果您不编写它,编译器会将 auto 推断为 const 限定类型。因此,在这种情况下,问题是:写auto const &amp; 是多余的吗?还是在某种意义上有所帮助?
  • @AndreiR.:“但不是第二个”废话
【解决方案3】:

代码不仅仅是供编译器阅读的——它也是供人类阅读的。只有在不以牺牲可读性为代价时,简洁才是好的。

在给定的代码中,推断常量是立即的。根据您提出的更改,推断 constness 需要读者准确记住或查看 book 的定义。

如果读者想要了解 constness,那么这里的简洁会适得其反。

【讨论】:

    【解决方案4】:

    看到关键字const 就清楚多了。您会立即意识到它不会被修改,如果没有 const 关键字,除非我们查看函数签名,否则我们不会知道。

    明确地使用const 关键字对代码的读者来说要好得多。


    此外,当您阅读下面的代码时,您会认为 x 是可修改的

    for (auto& x : book) { ... }
    

    然后当您尝试修改x 时,您会发现它会导致错误,因为book 是const。
    在我看来,这不是清晰的代码。最好在代码中声明x不会被修改,以便阅读代码的其他人期望正确。

    【讨论】:

      【解决方案5】:

      我想提出一个反驳点。在 Herb Sutter 在 CppCon 2014 "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 上,他指出“您应该知道您的变量是否为 const/volatile!”作为“auto&&”对局部变量不利的原因。

      基本上归结为意见问题。一些人认为显式有利于可读性或可维护性,但可能会提出相反的观点:冗余表明缺乏理解(C++ 规则或您的代码实际在做什么1)并可能造成损害可读性和可维护性。做任何你认为最好的事情。

      1:这是一个相当人为的例子,但 C++ 的经验法则在一般情况下并不真正起作用,而且很难记住。例如,Herb Sutter 建议您让构造函数参数按值传递,这与常规函数重载相反。这是const 无处不在的情况之一,可能会咬到你的脚,这取决于你是否同意 Herb。

      【讨论】:

      • 我认为您是在断章取义地引用它。在您发布的这段代码中,如果您在此处添加const 并使其看起来像auto const i = v.begin();,那么这里显式const 似乎是不正确的模式,因为例如您将无法使用++i。将const 添加到迭代器声明中不会使引用对象const,它会使迭代器本身成为const。
      • 这没有回答问题。
      【解决方案6】:

      提早提及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&lt;?&gt; const&amp; 通常过度指定您的接口,因为 vector&lt;?&gt; const&amp; 没有提供任何有用的东西,而 array_view&lt;?&gt; 没有提供,但可以强制从初始化列表或原始 C 数组等复制。)

      就编译器而言,没有理由使用const auto&amp; 而不是auto&amp; 或auto&amp;&amp;,因为所有3 个(在此上下文中)都被推断为相同类型。使用其中一个的唯一原因是与其他程序员交谈。

      在我的例子中,我默认使用auto&amp;&amp;(它表示我正在迭代,并不真正关心我在迭代什么)。

      auto&amp;如果我知道我需要修改,auto const&amp;如果我想强调我不修改。

      【讨论】:

        【解决方案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 限定符。

        【讨论】:

          猜你喜欢
          • 2012-05-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-09
          • 2012-02-01
          • 2018-10-22
          • 1970-01-01
          相关资源
          最近更新 更多