你用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)

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


当前回答

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

其他回答

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

错了。

它是关于自我记录你的代码和你的假设。

如果你的代码有很多人在工作,而且你的函数不是平凡的,那么你应该把const标记为任何你可以标记的东西。在编写工业级别的代码时,您应该始终假设您的同事都是精神病患者,他们试图以任何方式来对付您(特别是因为将来经常是您自己)。

此外,正如前面有人提到的,它可能会帮助编译器优化一些东西(尽管这是一个很长的机会)。

原因是形参的const只在函数内局部应用,因为它处理的是数据的副本。这意味着函数签名实际上是相同的。不过,经常这样做可能是不好的风格。

我个人倾向于不使用const,除了引用和指针形参。对于复制的对象来说,这并不重要,尽管它可以更安全,因为它表明了函数中的意图。这真的是一个主观判断。我倾向于使用const_iterator,但当循环某些东西时,我不打算修改它,所以我猜每个人都有自己的,只要严格维护引用类型的const正确性。

我知道这个问题“有点”过时了,但当我遇到它时,其他人可能也会在未来这样做... ...我仍然怀疑这个可怜的家伙会在这里列出我的评论:)

It seems to me that we are still too confined to C-style way of thinking. In the OOP paradigma we play around with objects, not types. Const object may be conceptually different from a non-const object, specifically in the sense of logical-const (in contrast to bitwise-const). Thus even if const correctness of function params is (perhaps) an over-carefulness in case of PODs it is not so in case of objects. If a function works with a const object it should say so. Consider the following code snippet

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

附注:你可能会认为(const)引用在这里更合适,并提供相同的行为。嗯,对的。只是给出了与我在其他地方看到的不同的画面……

关于编译器优化:http://www.gotw.ca/gotw/081.htm

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

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

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