Is it a good idea of maintaining “const-ness” as much as possible?
最近,我开发了一种实践,在我的代码中生成许多东西,比如
(1)函数的参数,我知道永远不会改变。例如。:
1 2 | void foo (const int i, const string s) ^^^^^ ^^^^^ |
(2)返回类型为
1 2 3 4 5 6 7 | struct A { ... const int foo () { return ...; } ^^^^^ operator const bool () const { return ...; } ^^^^^ }; |
(3)整数或字符串的简单计算。例如。:
1 2 3 4 | const uint size = vec.size(); ^^^^^ const string s2 = s1 +"hello"; ^^^^^ |
…以及其他一些地方。通常在其他现实世界代码中,我看不到这样的小尺度变量标记为
(1)和(3)密切相关。按值参数只是具有该名称的局部变量,计算结果也是如此。
通常,在短函数中,无论您是否标记局部变量
但是,有时它确实有帮助,因为它可以防止您意外地将它们传递给一个通过非常量引用获取其参数的函数,而不会意识到您在修改变量。因此,如果您在变量的生命周期中将其作为函数参数传递,那么将其标记为
有时,标记一个变量
(2)是另一回事。正如其他人所解释的,对于内置类型,它没有什么区别。对于类类型,不要按常量值返回。这似乎是一个好主意,因为它可以防止用户编写像
同样在C++ 03中,它没有移动语义,它阻止了被称为"SimaPimTimes"的技巧——具有非const返回值,我可以编写EDCOX1,8,而不是EDCOX1×6。
型
对。
用编程语言表达您的意图始终是一种很好的编程实践。你不打算改变变量,所以把它设为常量。稍后,当编译器对您大喊大叫说当它是常量时您不能修改它时,您会很高兴编译器发现了您自己设计的一些错误想法。这不仅对const是正确的,而且对于许多其他事物来说也是如此,这就是为什么在C++ 11中引入了EDCOX1×0 }关键字。
当然,在某些情况下,const不会改变任何东西,比如当你返回一个
型
忽略标量返回类型上的
也就是说,以下两个声明完全相同:
1 2 | int foo(); const int foo(); // this particular const is ignored by the compiler |
如果你想一想,这是有道理的:无论如何,没有
一般来说,常量正确性很重要——但是您使用的示例并不是它真正重要的地方。例如:
1 | void foo (const int i, const string s) |
我敢说这个原型是错的。如果你有原型的话
1 | void foo (int i, string s) |
那么,这已经是一个承诺,不修改用户为
现在,使用EDOCX1[0]实际上是有用的:
1 | void foo (int i, const string &s) |
这保留了您不修改
其他一些用途很好:
1 | const size_t size = vec.size(); |
这很好。(一旦你用
1 | const string s2 = s1 +"hello"; |
这也不错。不过,我还是会把它作为参考:
1 | const string &s2 = s1 +"hello"; |
这一个
1 | operator const bool () const { return ...; } |
是最无关紧要的。你可能应该执行
但对于具有非常量版本的类来说,这可能很重要。返回
(1) Arguments to function, which I know never going to be changed.
通过值
1 2 3 4 | void f( int x ) { x += 2; // OOPS: At this point 'x' is actually not what the caller passed to us! } |
如果你以
1 2 3 4 5 | void f( const std::string &s ) { std::string s2 = s; s2[0] = 'A'; // ... } |
他们这样做
1 2 3 4 | void f( std::string s ) { s[0] = 'A'; // ... } |
故意摆弄这种值的副本的好的副作用是,您不必考虑烦人的命名问题(参数应该是
由于这里的
(2) Return types as const.
只有当您认为代码的调用方可能无意中尝试修改函数返回的值时,这才有意义,如下所示:
1 | QString f() { return"Hello"; } |
如果你想禁止
(3) Trivial computation of integer or strings.
这实际上与我上面讨论的(1)完全相同。同样的道理也适用。
型
让我们来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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可以起到作用…因为可能有一些操作可以在非const对象上执行,而不能在const对象上执行。不过,这可能会被一个const-copy构造函数破坏。以getfoo()为例:
1 2 3 4 5 6 7 | Foo foo; /* foo.getFoo().setX(10); */ // ERROR! // because f.getFoo() returns const Foo Foo bar; bar = foo.getFoo(); bar.setX(10); // no error |
号
使by-value pod参数类型const可以防止在函数内修改该参数。例如,您不能在
所有的东西都是相等的,那就好了……因为在函数内部取一个int参数并更改它是一种令人困惑的做法;如果您正在调试并且想要知道函数是用什么调用的,那么您必须向调用堆栈上查找。但所有的东西都不一样,因为它更容易打字和屏幕混乱。
同样地,使局部POD变量变为常量会增加类型和混乱,从而减少回报。我只在全球范围内使用。
所以对我来说,const应该保留给具有真正不同语义方法分派(通常由缺少const-copy构造函数表示)或全局的对象。否则,这只是为了小利益或没有利益。
型
当然,至少对于需要长期保持可维护性的代码来说是这样的。
如果您想了解更多有关这方面的信息,Scott Meyers的"有效C++"显示了您需要const返回类型和方法参数来保护代码免遭滥用的恶劣场景。