你用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形参时,它会确保形参中使用的变量在函数体中没有被修改。为什么会有人想让一个按值参数作为常数呢?: -)

其他回答

我不使用const作为传递值的参数。调用者并不关心你是否修改参数,这是一个实现细节。

真正重要的是,如果方法没有修改其实例,则将其标记为const。这样做,因为否则你可能会得到大量的const_cast<>,或者你可能会发现标记一个方法const需要更改大量的代码,因为它调用了其他应该标记为const的方法。

如果我不需要修改局部变量,我也倾向于将它们标记为const。我相信,通过更容易地识别“移动部分”,可以使代码更容易理解。

从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混乱的眼中钉、一个烦人的唠叨、一个肤浅而无意义的承诺、一个不必要的障碍,并且偶尔会导致非常危险的错误。

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.

如果使用->*或。*操作符,这是必须的。

它会阻止你写出

void foo(Bar *p) { if (++p->*member > 0) { ... } }

我刚才差点就这么做了,但这可能不是你想要的结果。

我想说的是

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

如果我在Bar *和p之间放了一个const,编译器会告诉我。

当参数按值传递时,Const是没有意义的,因为你不会修改调用者的对象。

通过引用传递时应优先使用Const,除非函数的目的是修改传递的值。

最后,不修改当前对象(this)的函数可以,也可能应该声明为const。下面是一个例子:

int SomeClass::GetValue() const {return m_internalValue;}

这是一个不修改应用此调用的对象的承诺。换句话说,你可以调用:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

如果函数不是const,则会导致编译器警告。