在成员函数末尾添加 const 是一个好习惯吗 - 在适当的情况下?

发布于 2024-11-03 16:57:19 字数 1429 浏览 5 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

葬心 2024-11-10 16:57:19

请参阅 Herb Sutter(担任 C++ 标准委员会秘书 10 年)撰写的这篇关于 const 正确性的优秀文章

关于优化,他后来写了这篇文章,其中他指出“const 主要是为人类服务的,而不是为编译器和优化器服务的”。优化是不可能的,因为“太多的事情可能会出错......[你的函数]可能会执行 const_casts。”

然而,const 正确性是一个好主意,原因有两个:它是一个便宜的(就您的时间而言)断言,可以发现错误;并且,它表明函数理论上不应修改对象的外部状态,这使得代码更容易理解。

Please see this excellent article about const correctness by Herb Sutter (C++ standards committee secretary for 10 years.)

Regarding optimizations, he later wrote this article where he states that "const is mainly for humans, rather than for compilers and optimizers." Optimizations are impossible because "too many things could go wrong...[your function] might perform const_casts."

However, const correctness is a good idea for two reasons: It is a cheap (in terms of your time) assertion that can find bugs; and, it signals the intention that a function should theoretically not modify the object's external state, which makes code easier to understand.

木格 2024-11-10 16:57:19

每次函数不修改对象时,即每次函数“符合”const 时?

在我看来,是的。它确保您在涉及该对象的 const 对象或 const 表达式上调用此类函数:

void f(const A & a)
{
   a.inspect(); //inspect must be a const member function.
}

即使它修改一个或几个内部变量一次或两次,即使这样我通常也会这样做const 成员函数。这些变量是用 mutable 关键字声明的:

class A
{
     mutable bool initialized_cache; //mutable is must!

     public:
         void inspect() const //const member function
         {
               if ( !initialized_cache)
               {
                    initialized_cache= true;
                    //todo: initialize cache here
               }
               //other code
         }
};

every time the function does not modify the object, i.e., every time the function is 'eligible' for const?

In my opinion, Yes. It ensures that you call such functions on const objects or const expressions involving the object:

void f(const A & a)
{
   a.inspect(); //inspect must be a const member function.
}

Even if it modifies one or few internal variables once or twice, even then I usually make it const member function. And those variables are declared with mutable keyword:

class A
{
     mutable bool initialized_cache; //mutable is must!

     public:
         void inspect() const //const member function
         {
               if ( !initialized_cache)
               {
                    initialized_cache= true;
                    //todo: initialize cache here
               }
               //other code
         }
};
她如夕阳 2024-11-10 16:57:19

是的。一般来说,每个逻辑上为 const 的函数都应该设为 const。唯一的灰色区域是通过指针修改成员的地方(可以将其设置为 const,但可以说不应该是 const),或者修改用于缓存计算但没有效果的成员(可以说应该是设为 const,但需要使用关键字 mutable 来执行此操作)。

使用 const 这个词非常重要的原因是:

  1. 它对其他开发人员来说是重要的文档。开发人员会假设任何标记为 const 的内容都不会改变对象(这就是为什么在通过指针对象改变状态时使用 const 可能不是一个好主意),并且会假设任何未标记为 const 的内容都会发生改变。

  2. 它将导致编译器捕获无意的突变(如果标记为 const 的函数无意中调用非常量函数或突变元素,则会导致错误)。

Yes. In general, every function that is logically const should be made const. The only gray areas are where you modify a member through a pointer (where it can be made const but arguably should not be const) or where you modify a member that is used to cache a computation but otherwise has no effect (which arguably should be made const, but will require the use of the keyword mutable to do so).

The reason why it's incredibly important to use the word const is:

  1. It is important documentation to other developers. Developers will assume that anything marked const does not mutate the object (which is why it might not be a good idea to use const when mutating state through a pointer object), and will assume that anything not marked const mutates.

  2. It will cause the compiler to catch unintentional mutations (by causing an error if a function marked const unintintionally calls a non-const function or mutates an element).

面如桃花 2024-11-10 16:57:19

是的,这是一个很好的做法。

在软件工程层面,它允许您拥有只读对象,例如,您可以通过将对象设为常量来防止对象被修改。如果一个对象是 const,则只允许在其上调用 const 函数。

此外,我相信如果编译器知道一个对象只会被读取(例如,在对象的多个实例之间共享公共数据,因为我们知道它们永远不会被修改),那么编译器可以进行某些优化。

Yes, it is a good practice.

At the software engineering level it allows you to have read-only objects, e.g. you can prevent objects from being modified by making them const. And if an object is const, you are only allowed to call const functions on it.

Furthermore, I believe the compiler can make certain optimizations if it he knows that an object will only be read (e.g., share common data between several instances of the object as we know they are never being modified).

掩耳倾听 2024-11-10 16:57:19

“const”系统是 C++ 真正混乱的功能之一。它的概念很简单,添加了“const”声明的变量成为常量,并且不能由程序更改,但是,必须用它来替代 C++ 缺少的功能之一,这会变得可怕复杂且令人沮丧的限制。下面尝试解释“const”的使用方式以及它存在的原因。在指针和“const”的混合中,指向变量的常量指针对于可以更改值但不能在内存中移动的存储非常有用,而指针(常量或其他)对于从函数返回常量字符串和数组非常有用,因为它们是作为指针实现的,否则程序可能会尝试更改并崩溃。在编译期间将检测到更改不可更改值的尝试,而不是难以追踪的崩溃。

例如,如果返回固定“Some text”字符串的函数的编写方式如下

char *Function1()
{ return “Some text”;}

,则如果程序意外尝试更改值,则程序可能会崩溃,

Function1()[1]=’a’;

而如果编写了原始函数,则编译器会发现错误,

 const char *Function1()
 { return "Some text";}

因为编译器然后就会知道该值是不可更改的。 (当然,理论上编译器无论如何都可以解决这个问题,但 C 并没有那么聪明。)
当使用参数调用子例程或函数时,作为参数传递的变量可能会被读取以将数据传输到子例程/函数中,写入以将数据传输回调用程序或两者兼而有之。有些语言允许直接指定这一点,例如“in:”、“out:”和“in:”。 'inout:' 参数类型,而在 C 中,必须在较低级别工作并指定传递变量的方法,选择一种也允许所需数据传输方向的方法。

例如,像这样的子例程

void Subroutine1(int Parameter1)
{ printf("%d",Parameter1);}

接受默认 C& 中传递给它的参数。 C++方式,这是一个副本。因此,子例程可以读取传递给它的变量的值,但不能更改它,因为它所做的任何更改都只会对副本进行,并在子例程结束时丢失,因此

void Subroutine2(int Parameter1)
{ Parameter1=96;}

调用它的变量将保持不变,不会设置为 96

。添加“&”到 C++ 中的参数名称(这是一个非常令人困惑的符号选择,因为 C 中其他地方的变量前面的“&”会生成指针!)会导致实际变量本身而不是副本用作参数子例程,因此可以写入,从而将数据传回子例程。因此,

void Subroutine3(int &Parameter1) 
{ Parameter1=96;}

将调用它的变量设置为 96。这种将变量作为其本身而不是副本传递的方法在 C 中称为“引用”。

这种传递变量的方式是 C++ 对 C 的补充。传递可变变量在原始 C 语言中,使用了一种相当复杂的方法,该方法使用指向变量的指针作为参数,然后更改它所指向的内容。例如,

void Subroutine4(int *Parameter1) 
{ *Parameter1=96;}

可以工作,但需要在被调用例程中每次使用变量,并更改调用例程以传递指向变量的指针,这是相当麻烦的。

但是“const”是从哪里来的呢?好吧,还有第二种常见用途,即通过引用或指针而不是复制来传递数据。也就是说,复制变量会浪费太多内存或花费太长时间。对于大型复合用户定义变量类型(C 中的“结构”和 C++ 中的“类”)尤其有可能出现这种情况。因此声明的子例程

void Subroutine4(big_structure_type &Parameter1);

可能使用“&”因为它将更改传递给它的变量,或者可能只是为了节省复制时间,并且如果该函数是在其他人的库中编译的,则无法判断它是什么。如果需要相信子例程不会更改变量,这可能是一种风险。

为了解决这个问题,可以在参数列表中使用“const”,

void Subroutine4(big_structure_type const &Parameter1);

这样会导致变量在不复制的情况下传递,但会阻止它被更改。这很混乱,因为它本质上是从双向变量传递方法创建了仅内变量传递方法,而双向变量传递方法本身又是从仅内变量传递方法创建的,只是为了欺骗编译器进行一些优化。

理想情况下,程序员不需要控制精确指定变量如何传递的细节,只需说出信息的方向并让编译器自动对其进行优化,但 C 是为在功能较弱的计算机上进行原始低级编程而设计的这不是当今的标准,因此程序员必须明确地执行此操作。

The 'const' system is one of the really messy features of C++. It is simple in concept, variables declared with ‘const’ added become constants and cannot be altered by the program, but, in the way is has to be used to bodge in a substitute for one of the missing features of C++, it gets horridly complicated and frustratingly restrictive. The following attempts to explain how 'const' is used and why it exists. Of the mixes of pointers and ‘const’, the constant pointer to a variable is useful for storage that can be changed in value but not moved in memory and the pointer (constant or otherwise) is useful for returning constant strings and arrays from functions which, because they are implemented as pointers, the program could otherwise try to alter and crash. Instead of a difficult to track down crash, the attempt to alter unalterable values will be detected during compilation.

For example, if a function which returns a fixed ‘Some text’ string is written like

char *Function1()
{ return “Some text”;}

then the program could crash if it accidentally tried to alter the value doing

Function1()[1]=’a’;

whereas the compiler would have spotted the error if the original function had been written

 const char *Function1()
 { return "Some text";}

because the compiler would then know that the value was unalterable. (Of course, the compiler could theoretically have worked that out anyway but C is not that clever.)
When a subroutine or function is called with parameters, variables passed as the parameters might be read from to transfer data into the subroutine/function, written to to transfer data back to the calling program or both to do both. Some languages enable one to specify this directly, such as having ‘in:’, ‘out:’ & ‘inout:’ parameter types, whereas in C one has to work at a lower level and specify the method for passing the variables choosing one that also allows the desired data transfer direction.

For example, a subroutine like

void Subroutine1(int Parameter1)
{ printf("%d",Parameter1);}

accepts the parameter passed to it in the default C & C++ way which is a copy. Therefore the subroutine can read the value of the variable passed to it but not alter it because any alterations it makes are only made to the copy and lost when the subroutine ends so

void Subroutine2(int Parameter1)
{ Parameter1=96;}

would leave the variable it was called with unchanged not set to 96.

The addition of an ‘&’ to the parameter name in C++ (which was a very confusing choice of symbol because an ‘&’ infront of variables elsewhere in C generate pointers!) like causes the actual variable itself, rather than a copy, to be used as the parameter in the subroutine and therefore can be written to thereby passing data back out the subroutine. Therefore

void Subroutine3(int &Parameter1) 
{ Parameter1=96;}

would set the variable it was called with to 96. This method of passing a variable as itself rather than a copy is called a ‘reference’ in C.

That way of passing variables was a C++ addition to C. To pass an alterable variable in original C, a rather involved method using a pointer to the variable as the parameter then altering what it pointed to was used. For example

void Subroutine4(int *Parameter1) 
{ *Parameter1=96;}

works but requires the every use of the variable in the called routine so altered and the calling routine altered to pass a pointer to the variable which is rather cumbersome.

But where does ‘const’ come into this? Well, there is a second common use for passing data by reference or pointer instead of copy. That is when copying a the variable would waste too much memory or take too long. This is particularly likely with large compound user-defined variable types (‘structures’ in C & ‘classes’ in C++). So a subroutine declared

void Subroutine4(big_structure_type &Parameter1);

might being using ‘&’ because it is going to alter the variable passed to it or it might just be to save copying time and there is no way to tell which it is if the function is compiled in someone else’s library. This could be a risk if one needs to trust the the subroutine not to alter the variable.

To solve this, ‘const’ can be used the in the parameter list like

void Subroutine4(big_structure_type const &Parameter1);

which will cause the variable to passed without copying but stop it from then being altered. This is messy because it is essentially making an in-only variable passing method from a both-ways variable passing method which was itself made from an in-only variable passing method just to trick the compiler into doing some optimization.

Ideally, the programmer should not need control this detail of specifying exactly how it variables are passed, just say which direction the information goes and leave the compiler to optimize it automatically, but C was designed for raw low-level programming on far less powerful computers than are standard these days so the programmer has to do it explicitly.

世俗缘 2024-11-10 16:57:19

我的理解是,它确实只是一个标志。然而,话虽如此,您还是希望尽可能地添加它。如果您未能添加它,并且代码中其他地方的函数会执行类似的操作

void function(const MyClass& foo)
{
   foo.getData();
}

,您将遇到问题,因为编译器无法保证 getData 不会修改 foo。

My understanding is that it is indeed just a flag. However, that said, you want to add it wherever you can. If you fail to add it, and a function elsewhere in your code does something like

void function(const MyClass& foo)
{
   foo.getData();
}

You will run into issues, for the compiler cannot guarantee that getData does not modify foo.

与君绝 2024-11-10 16:57:19

将成员函数设置为 const 可确保调用具有 const 对象的代码仍然可以调用该函数。它与编译器检查有关 - 这有助于创建强大的自文档代码并避免对象的意外修改 - 而不是与运行时性能有关。所以是的,如果函数的性质不需要修改对象的可观察值(它仍然可以修改显式以 mutable 关键字为前缀的成员变量,那么您应该始终添加 const ,它适用于一些奇怪的用途,例如不影响对象的客户端可见行为的内部缓存和计数器)。

Making member functions const ensures that calling code that has const objects can still call the function. It is about this compiler check - which helps create robust self-documenting code and avoid accidental modifications of objects - and not about run-time performance. So yes, you should always add const if the nature of the function is such that it doesn't need to modify the observable value of the object (it can still modify member variables explicitly prefixed with the mutable keyword, which is intended for some quirky uses like internal caches and counters that don't affect the client-visible behaviour of the object).

倾`听者〃 2024-11-10 16:57:19

我不同意这里的一些回复。因为可以而简单地将函数声明为 const 并不是一个好主意。正如 Herb Stutter 在这篇 文章 中所写的顶部回复中提到的“const 主要针对人类,而不是编译器和优化器。” 20 年后,这仍然是事实。 const 声明告诉调用者可以安全地调用该函数,而无需修改状态。现在,编译器只关心函数所属对象的状态修改。

C++ 没有良好的纯度概念。也就是说,编译器没有明确的方法来真正了解函数是否有副作用。一个简单的例子是增加数据库中列的成员函数。编译器会告诉您将此函数声明为 const 是安全的,因为它不会修改对象本身的状态。但这样做可能会导致该功能的误用。我认为你不应该仅仅依靠编译器来告诉你是否应该将函数声明为 const。相反,您应该考虑该函数的用途以及您期望如何使用它。

I disagree with some of the responses here. Simply declaring a function const because you can is not a good idea. As mentioned in the top response Herb Stutter writes in this article "const is mainly for humans, rather than for compilers and optimizers." 20 years later and this is still true. A const declaration tells the caller that this function can be safely be called without a state modification. Now, the compiler only cares about state modifications to the object to which the function belongs.

C++ has no good notion of purity. That is there is no clean way for the compiler to really know if a function has side-effects. A simple example would be a member function that increments a column in a database. The compiler would tell you it's safe to declare this function as const since it does not modify the state of the object itself. But doing so could result in misuse of this function. I don't think you should just rely on the compiler to tell you if you should declare a function as const. Instead you should consider what the function does and how you expect it to be used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文