你用const能做到什么程度?你只是在必要的时候才把函数变成const,还是从头到尾到处都用它?例如,想象一个简单的变异子,它接受一个布尔参数:

void SetValue(const bool b) { my_val_ = b; }

这个const真的有用吗?就我个人而言,我选择广泛地使用它,包括参数,但在这种情况下,我想知道它是否值得?

我还惊讶地发现,你可以在函数声明中的形参中省略const,但可以在函数定义中包含它,例如:

. h文件

void func(int n, long l);

. cpp文件

void func(const int n, const long l)

这有什么原因吗?这对我来说有点不寻常。


当前回答

我倾向于尽可能使用const。(或其他适合目标语言的关键字。)我这样做纯粹是因为它允许编译器做额外的优化,否则它无法做。因为我不知道这些优化可能是什么,我总是这样做,即使它看起来很傻。

据我所知,编译器很可能看到一个const值形参,然后说:“嘿,这个函数无论如何都没有修改它,所以我可以通过引用传递并节省一些时钟周期。”我不认为它会做这样的事情,因为它改变了函数的签名,但它说明了这一点。也许它会做一些不同的堆栈操作之类的…重点是,我不知道,但我知道试图比编译器更聪明只会让我感到羞耻。

c++有一些额外的包袱,有常量正确性的思想,所以它变得更加重要。

其他回答

从API的角度来看,多余的const是不好的:

在代码中为通过value传递的内在类型参数添加多余的const会使API变得混乱,同时对调用者或API用户没有任何有意义的承诺(它只会阻碍实现)。

在一个不需要的API中有太多的“const”就像“狼来了”一样,最终人们会开始忽略“const”,因为它到处都是,大多数时候没有任何意义。

在API中,将"推理还原"参数转化为额外的const参数对前两点来说是好的,如果更多的const参数是好的,那么每个可以带有const参数的参数都应该带有const参数。事实上,如果它真的那么好,你会希望const是参数的默认值,只有当你想改变形参时,才会使用像“mutable”这样的关键字。

因此,让我们尝试在任何可能的地方输入const:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

考虑上面的代码行。不仅声明更混乱、更长、更难阅读,而且API用户可以安全地忽略四个“const”关键字中的三个。然而,额外使用“const”使得第二行有潜在的危险!

Why?

对第一个参数char * const buffer的快速误读可能会使您认为它不会修改传入的数据缓冲区中的内存——然而,这不是真的!当快速扫描或误读时,多余的“const”可能会导致对API的危险和错误假设。


从代码实现的角度来看,多余的const也是不好的:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

如果FLEXIBLE_IMPLEMENTATION不为真,那么API“承诺”不以下面的第一种方式实现函数。

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

这是一个非常愚蠢的承诺。为什么要做出一个对调用者毫无好处、只会限制实现的承诺呢?

这两个都是同一个函数的完全有效实现,所以你所做的一切都是不必要的束手束脚。

此外,这是一个非常肤浅的承诺,很容易(并且在法律上被规避)。

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

看,我无论如何都是这样实现的,尽管我承诺不会这样做——只是使用一个包装器函数。这就像电影里的坏人承诺不杀人,却命令他的追随者去杀了他们。

那些多余的const值不过是电影里坏人的一个承诺。


但撒谎的能力更糟:

我已经被启发,你可以不匹配的const头(声明)和代码(定义)使用虚假的const。喜欢使用const的人声称这是一件好事,因为它允许你只在定义中使用const。

// Example of const only in definition, not declaration
struct foo { void test(int *pi); };
void foo::test(int * const pi) { }

然而,反之亦然。您可以只在声明中放入伪const,而在定义中忽略它。这只会让API中多余的const变得更糟糕,更像是一个可怕的谎言——请看下面的例子:

struct foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

多余的const实际上只会在实现者想要更改变量或通过非const引用传递变量时,迫使他使用另一个本地副本或包装器函数,从而降低其代码的可读性。

看看这个例子。哪个更有可读性?第二个函数中额外变量的唯一原因是某个API设计者抛出了一个多余的const,这是显而易见的吗?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

希望我们学到了一些东西。多余的const是一个让api混乱的眼中钉、一个烦人的唠叨、一个肤浅而无意义的承诺、一个不必要的障碍,并且偶尔会导致非常危险的错误。

在你提到的情况下,它不会影响API的调用者,这就是为什么它不常做(并且在头文件中也没有必要)。它只影响函数的实现。

这并不是一件特别糟糕的事情,但考虑到它不会影响您的API,它的好处并不是那么大,而且它增加了类型,所以通常不这样做。

If the parameter is passed by value (and is not a reference), usually there is not much difference whether the parameter is declared as const or not (unless it contains a reference member -- not a problem for built-in types). If the parameter is a reference or pointer, it is usually better to protect the referenced/pointed-to memory, not the pointer itself (I think you cannot make the reference itself const, not that it matters much as you cannot change the referee). It seems a good idea to protect everything you can as const. You can omit it without fear of making a mistake if the parameters are just PODs (including built-in types) and there is no chance of them changing further along the road (e.g. in your example the bool parameter).

I didn't know about the .h/.cpp file declaration difference, but it does make some sense. At the machine code level, nothing is "const", so if you declare a function (in the .h) as non-const, the code is the same as if you declare it as const (optimizations aside). However, it helps you to enlist the compiler that you will not change the value of the variable inside the implementation of the function (.ccp). It might come handy in the case when you're inheriting from an interface that allows change, but you don't need to change to parameter to achieve the required functionality.

做一个VB。NET程序员需要使用具有50多个公开函数的c++程序,以及偶尔使用const限定符的.h文件,很难知道何时使用ByRef或ByVal访问变量。

当然,程序通过在出错的行上生成一个异常错误来告诉您,但是随后您需要猜测2-10个参数中哪一个是错误的。

所以现在我有一个令人讨厌的任务,试图说服开发人员,他们应该真正定义他们的变量(在.h文件中),以一种允许自动创建所有VB的方法。NET函数定义容易。然后他们会自鸣得意地说:“读……文档”。

我写了一个awk脚本,它解析一个.h文件,并创建所有的Declare Function命令,但没有指示哪个变量是R/O vs R/W,它只完成了一半的工作。

编辑:

在另一位用户的鼓励下,我添加了以下内容;

下面是一个(IMO)格式不佳的.h条目的例子;

typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );

从我的脚本的结果VB;

    Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer

注意,第一个参数中缺少“const”。如果没有它,程序(或其他开发人员)就不知道第一个参数应该传递“ByVal”。通过添加“const”,它使.h文件自文档化,以便使用其他语言的开发人员可以轻松地编写工作代码。

确实没有理由将值形参设为“const”,因为函数只能修改变量的副本。

使用“const”的原因是如果你通过引用传递更大的东西(例如有很多成员的结构体),在这种情况下,它确保函数不能修改它;或者更确切地说,如果您试图以常规方式修改它,编译器将报错。它可以防止它被意外修改。