【问题标题】:Is it a good idea of maintaining "const-ness" as much as possible?尽可能保持“常量”是个好主意吗?
【发布时间】:2011-12-16 09:23:58
【问题描述】:

最近,我一直在开发一种在我的代码中制作许多东西的做法const

(1) 函数参数,我知道它永远不会改变。例如:

void foo (const int i, const string s)
          ^^^^^        ^^^^^ 

(2) 返回类型const。例如:

struct A {
...
  const int foo () { return ...; }
  ^^^^^
  operator const bool () const { return ...; }
           ^^^^^
};

(3) 整数或字符串的简单计算。例如:

const uint size = vec.size();
^^^^^
const string s2 = s1 + "hello ";
^^^^^

...还有其他几个地方。通常在其他现实世界的代码中,我看不到标记为const 的小规模变量。但我想,让他们const 永远不会有害。这是一种好的编程实践吗?

【问题讨论】:

  • 我仍然喜欢将它用于按值传递的对象,我喜欢我的参数来指示传入的原始值。我讨厌调试嵌套函数,其中i 的值从一个函数发生变化到下一个。至少如果使用了新变量,您可以更容易地发现它。
  • @MarkIngram:所以基本上你是说你更喜欢unsigned plus(const unsigned a, const unsigned b) { if (b) return plus(a+1,b-1); return a; } 而不是unsigned plus(unsigned a, unsigned b) { if (b) { ++a; --b; return plus(a,b) } return a; }?因为在第二个代码中,它看起来好像您正在将参数传递给下一个函数(在这种情况下是一个递归函数),而实际上您正在传递修改后的值?听起来很公平。
  • 是的,我认为不需要修改该(第二个)示例中的变量。
  • 请参阅 this Q&A 了解为什么 const 返回类型不是一个好主意

标签: c++ coding-style constants


【解决方案1】:

(1) 和 (3) 密切相关。按值参数只是具有该名称的局部变量,就像您的计算结果一样。

通常,无论您是否标记局部变量const,短函数几乎没有什么区别,因为您可以在您面前看到它们的整个范围。您可以查看值是否更改,您不需要或不希望编译器强制执行它。

但有时它确实会有所帮助,因为它可以防止您意外地将它们传递给通过非常量引用获取其参数的函数,而不会意识到您正在修改您的变量。因此,如果您在变量生命周期内将其作为函数参数传递,那么将其标记为 const 可以让您更有信心知道它之后的价值。

非常偶尔,标记变量 const 可以帮助优化器,因为您告诉它对象永远不会被修改,有时这是真的,但编译器无法以其他方式证明它.但出于这个原因,它可能不值得这样做,因为在大多数情况下它没有任何区别。

(2) 是另一回事。正如其他人所解释的那样,对于内置类型没有区别。对于类类型,不要按 const 值返回。这似乎是一个好主意,因为它可以防止用户编写像func_returning_a_string() += " extra text"; 这样毫无意义的东西。但它也阻止了一些有意义的事情——C++11 移动语义。如果foo 返回一个常量字符串,我写std::string s = "foo"; if (condition) s = foo();,那么我在s = foo(); 得到复制分配。如果foo 返回一个非常量字符串,那么我会得到移动分配。

类似地,在没有移动语义的 C++03 中,它可以防止被称为“swaptimization”的技巧 - 使用非常量返回值,我可以编写 foo().swap(s); 而不是 s = foo();

【讨论】:

  • 对交换优化很感兴趣。但是真的没有通过 const 值返回类的合法案例吗?作为 const 返回给调用者一个具有更多限制性访问的对象,它带走的不仅仅是交换......可能有一些操作被带走是有充分理由的吗?我认识到,如果一个按值返回的类“说明”了它的用法并且它可能应该是可复制的,我只是想知道是否有一个证明声明说返回它 const 永远没有意义?如果没有 const 分配/构造 const 实际上“粘”。
  • @HostileFork:某处可能有一个特殊情况——某种代理对象,也许,用户不应该关心它,除了它允许满足某个概念所需的一些表达式。但是,如果对象具有模糊的正常值语义,并且您返回 const 实例,那么用户可以将其复制到他们自己的非常量实例,然后对其进行变异。既然他们可以做到这一点,我认为如果他们可以改变返回值本身会更方便,这也是“他们的”,因为它是他们表达中的暂时性。
  • @SteveJessop 这就是我在回答中提到的:如果有 const 赋值和复制构造函数,那么尝试将其 const 化几乎没有意义。所以也许这是一个很好的试金石......
【解决方案2】:

是的。

用编程语言表达你的意图总是一种很好的编程习惯。您不打算更改变量,因此将其设为 const。稍后,当你的编译器对你大喊“当它是 const 时你不能修改它”时,你会很高兴编译器发现了你自己设计的一些错误想法。这不仅适用于 const,而且适用于许多其他事物,例如为什么在 C++11 中引入了 override 关键字。

当然,在某些情况下 const 不会改变任何东西,比如当你返回 int 时,但就像在其他安全领域一样:最好有一个 const 太多(你可以稍后删除,因为它不是真的需要),而不是太少(这会突然安静地破坏东西)。

【讨论】:

    【解决方案3】:

    标量返回类型上的consts 被忽略,因为没有标量 const rvalue 这样的东西。

    也就是说,下面两个声明是完全等价的:

    int foo();
    const int foo();   // this particular const is ignored by the compiler
    

    如果你仔细想想,这是有道理的:无论如何,如果没有const,你就不能写foo() = 42;

    【讨论】:

    • +1,你是对的。但是,我不记得了,但很久以前我注意到某处foo() = 42; 在某个编译器中默默地工作(可能是错误的编译器)。这也让我对return 类型产生了偏执!
    【解决方案4】:

    一般来说,常量的正确性很重要——但您使用的示例并不是真正重要的地方。例如:

    void foo (const int i, const string s)
    

    我会如此大胆地宣布这个原型错误。如果你只有原型

    void foo (int i, string s)
    

    那么这已经承诺不会修改用户为is 传递的值。 (因为您获得了这些值的副本,而不是对原始值的引用)因此,const 在这种情况下唯一要做的就是请求编译器在您不小心尝试修改 @ 时进行投诉987654326@ 或s。但是还有其他方法可以达到这种效果——它实际上并没有任何业务暴露在外部。

    现在,const 的以下用法实际上很有用:

    void foo (int i, const string &s)
    

    这保留了您不修改 s 的承诺,但现在您可以获得性能优势,因为它不再涉及制作 s 的副本。

    其他一些用途很好:

    const size_t size = vec.size();
    

    这很好。 (一旦将uint 替换为size_t)这里没有性能优势,但它是自我记录的,可以保护您免受愚蠢的事故。

    const string s2 = s1 + "hello ";
    

    这也不错。虽然我可能会再次将其作为参考:

    const string &s2 = s1 + "hello ";
    

    这个

    operator const bool () const { return ...; }
    

    是最不相关的。您可能应该实现operator bool 而不是operator const bool,即使除了前者之外没有其他原因是人们所期望的。

    但这对于具有非常量版本的类可能很重要。返回const,则不是品味或风格的问题,而是声明“我要返回给你一个新对象,但你不能调用它的任何非常量函数”。这通常是个坏主意——只有当你真的要声明时才写const

    【讨论】:

      【解决方案5】:

      我们来看一个例子:

      class Foo {
      private:
          int x;
      public:
          Foo () : x (0) { }
          Foo (const Foo& other) { x = other.x; }
          void setX(const int newX) {
              x = newX;
          }
          const int getX() const {
              return x;
          }
          const Foo getFoo() const {
              return *this;
          }
      };
      

      让 getX() 返回 const intint 是愚蠢的。任何作为 const 的 POD 按值返回类型都是没有意义的。为什么会很重要?对非 const POD 变量唯一不能对 const 做的就是赋值……而左值/右值的区别负责返回类型。

      相比之下,当它是一个按值返回的对象时,const 可以有所作为...因为您可能对非 const 对象执行一些您不能执行的操作在一个常量上。不过,这可能会被 const 复制构造函数破坏。我们以 getFoo() 为例:

      Foo foo;
      /* foo.getFoo().setX(10); */ // ERROR!
      // because f.getFoo() returns const Foo
      
      Foo bar;
      bar = foo.getFoo();
      bar.setX(10); // no error
      

      创建一个按值的 POD 参数类型 const 可以防止在函数内修改该参数。例如,您不能在setX 内为newX 赋值。

      所有的事情都是平等的就可以了……因为在函数内部接受一个 int 参数并改变它是一种令人困惑的做法;如果您正在调试并且想知道函数被调用了什么,您必须向上调用堆栈找出。但并不是所有的事情都是平等的,因为它更多的是打字和屏幕混乱。

      类似地,将本地 POD 变量设为 const 会导致更多的输入和混乱,从而减少回报。我只在全局变量上使用它。

      所以对我来说,我说 const 应该保留给具有真正不同语义方法调度的对象(通常由缺少 const 复制构造函数表示)...或全局变量。否则,它只会妨碍微不足道或没有好处。

      【讨论】:

      • 没有。但是你可以检查定义和你的答案。
      • @curiousguy 您也可以停止在没有解释和深奥的情况下投反对票,并编写实际连贯的 cmets。有趣的是,当我发现我不理解的东西时,我会说“嗯,你是什么意思?”并真诚地进行宣传,并且您会表现出恶意作为回应。非常...“有趣”。
      • @curiousguy 很高兴它就在你我之间。说真的,伙计,真的很难解释自己而不是说“去读书,孩子”吗?我发誓。互联网人。
      • "任何 POD 作为 const 的按值返回类型都是没有意义的。这有什么关系?" 你是指基本类型吗?跨度>
      【解决方案6】:

      (1) 函数参数,我知道永远不会改变。

      让参数按值传递const 的唯一原因是您可能希望避免意外更改函数内副本的值,如下所示:

      void f( int x ) {
        x += 2;
        // OOPS: At this point 'x' is actually not what the caller passed to us!
      }
      

      如果您将参数传递为const int x,则不会发生这种情况。但是,我知道很多代码,程序员有意将参数作为值而不是 const 传递,因为他想要修改参数 - 否则他会复制反正。 IE。而不是

      void f( const std::string &s ) {
          std::string s2 = s;
          s2[0] = 'A';
          // ...
      }
      

      他们会的

      void f( std::string s ) {
          s[0] = 'A';
          // ...
      }
      

      像这样故意修改值的副本的好处是您不必考虑烦人的命名问题(参数应该是 s_ 并且经过清理的值是 s?还是 @ 987654328@ 与 s,还是什么?)。

      由于此处const 的存在与否根本不会影响调用者(无论如何您都会获得该值的副本,因此您无法修改调用者的数据),它简单地归结为to:有时它是有道理的。有时它不会。这里没有好的指导方针。我个人的感觉是你不应该打扰。如果你想让参数const 避免你不小心写它,那么你的函数可能一开始就太长了(或者参数名称很傻,比如i 或@ 987654333@ 或 it 之类的)。

      (2) 返回类型为 const。

      只有当您认为代码的调用者有可能意外尝试修改函数返回的值时,这才有意义,如下所示:

      QString f() { return "Hello"; }
      

      如果您想禁止f.strip() 或其他变异调用,您可以将return 输入const。我曾经知道一个很好的例子,即将对象作为非常量值返回实际上允许一些相当令人惊讶的客户端代码。可惜我已经记不起来了。

      (3) 整数或字符串的简单计算。

      这实际上与我上面讨论的 (1) 完全相同。同样的道理也适用。

      【讨论】:

        【解决方案7】:

        肯定是这样,至少对于需要长期保持可维护性的代码而言。

        如果您想了解更多有关这方面的信息,Scott Meyers 的“Effective C++”一书展示了许多需要 const 返回类型和方法参数来保护代码不被滥用的场景。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-05-29
          • 1970-01-01
          相关资源
          最近更新 更多