C++ “保持”是一个好主意吗;const ness“;尽可能多?

C++ “保持”是一个好主意吗;const ness“;尽可能多?,c++,coding-style,constants,C++,Coding Style,Constants,最近,我一直在开发一种实践,将代码中的许多内容作为const: (1) 函数的参数,我知道它永远不会被更改。e、 g: void foo (const int i, const string s) ^^^^^ ^^^^^ (2) 返回类型为const。e、 g: struct A { ... const int foo () { return ...; } ^^^^^ operator const bool () const { return .

最近,我一直在开发一种实践,将代码中的许多内容作为
const

(1) 函数的参数,我知道它永远不会被更改。e、 g:

void foo (const int i, const string s)
          ^^^^^        ^^^^^ 
(2) 返回类型
const
。e、 g:

struct A {
...
  const int foo () { return ...; }
  ^^^^^
  operator const bool () const { return ...; }
           ^^^^^
};
(3) 整数或字符串的平凡计算。e、 g:

const uint size = vec.size();
^^^^^
const string s2 = s1 + "hello ";
^^^^^
。。。几乎没有更多的地方。通常在其他真实代码中,我看不到标记为
const
的小规模变量。但我想,让它们成为常量永远不会有坏处。这是一种好的编程实践吗?

是的

用编程语言表达您的意图始终是一种很好的编程实践。您不想更改变量,所以将其设置为常量。稍后,当你的编译器对你大喊大叫,当它是常量时你不能修改它,你会很高兴编译器发现了你自己设计的一些错误想法。这不仅适用于const,也适用于许多其他情况,例如,在C++11中引入了
override
关键字


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

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


如果您想进一步了解这一点,Scott Meyers的《有效的C++》一书展示了一些非常重要的场景,在这些场景中,您需要常量返回类型和方法参数来保护代码不被误用

让我们看一个例子:

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 int
而不是一个
int
是愚蠢的。任何按值类型作为常量返回的POD都是无意义的。这有什么关系?对于非常量POD变量,唯一可以做的事情是赋值,而对于常量POD变量,唯一不能做的事情是赋值……对于返回类型,左值/右值的区别会考虑到这一点

相反,当它是一个按值返回的对象时,const可以起作用……因为可能有一些操作可以在非const对象上执行,而在const对象上则无法执行。不过,const copy构造函数可能会破坏这一点。以getFoo()为例:

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

Foo bar;
bar = foo.getFoo();
bar.setX(10); // no error
void foo (const int i, const string s)
设置by value POD参数type const可防止在函数内修改该参数。例如,您不能在
setX
内部为
newX
赋值

在所有条件相同的情况下,这是可以的……因为在函数中使用int参数并对其进行更改是一种令人困惑的做法;如果您正在调试,并且想知道调用了什么函数,那么您必须进入调用堆栈才能找到答案。但并非所有的事情都是一样的,因为打字和屏幕更混乱

类似地,将局部POD变量设置为const会更加类型化和混乱,从而获得更少的回报。我只在全局上使用它


所以对我来说,我认为const应该保留给具有真正不同语义方法分派的对象(通常表示为缺少const copy构造函数)…或全局对象。否则,它只会为次要的或没有好处而碍事。

const
s对标量返回类型的影响会被忽略,因为没有标量常量值这样的东西

也就是说,以下两个声明完全等效:

int foo();
const int foo();   // this particular const is ignored by the compiler
如果你仔细想想,这是有道理的:你不能写
foo()=42不带
常量

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

使用value
const
传递参数的唯一原因是,您可能希望避免意外更改函数内副本的值,如:

void f( int x ) {
  x += 2;
  // OOPS: At this point 'x' is actually not what the caller passed to us!
}
如果将参数作为
const int x
传递,则不会发生这种情况。然而,我知道很多代码,程序员故意将参数作为值传递,而不是常量,因为他想修改参数,否则他无论如何都会复制。即代替

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

void f( std::string s ) {
    s[0] = 'A';
    // ...
}
像这样故意修改值的副本的一个好的副作用是,您不必考虑恼人的命名问题(参数应该是
s_u
,经过消毒的值应该是
s
?或者
s2
s
,或者什么?)

由于此处是否存在
常量
根本不会影响调用者(无论如何,您都会得到一个值的副本,因此无法修改调用者的数据),因此它可以简单地归结为:在某些情况下,它是有意义的。有时候不是。这里没有好的指导方针。我个人的感觉是,你不应该麻烦。如果您想使参数
const
避免意外编写它,那么首先可能是函数太长(或者参数名称很傻,比如
i
tmp
it
之类)

(2) 返回类型为const

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

QString f() { return "Hello"; }
如果您想禁止
f.strip()
或其他变异调用,可以使
返回
类型
const
。我曾经知道一个很好的例子,在这个例子中,将对象作为非常量值返回实际上允许一些相当令人惊讶的客户端代码。不幸的是,我已经记不得了

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


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

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

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

Foo bar;
bar = foo.getFoo();
bar.setX(10); // no error
void foo (const int i, const string s)
我会大胆地宣布这件事
const size_t size = vec.size();
const string s2 = s1 + "hello ";
const string &s2 = s1 + "hello ";
operator const bool () const { return ...; }