c++ - 使用'const'作为函数参数


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

是否有一个原因? 这对我来说似乎有点不寻常。


Answers



原因是该参数的const只适用于本地函数内,因为它正在处理数据的副本。 这意味着函数签名实际上是相同的。 尽管这样做很可能是不好的风格。

我个人倾向于不使用const,除了引用和指针参数。 对于复制的对象,它并不重要,但它可以更安全,因为它表示函数内的意图。 这真是一个判断力的呼唤。 我确实倾向于使用const_iterator,虽然在循环时我不打算修改它,所以我想每个人都可以,只要const参考类型的正确性得到严格维护。




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

错误。

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

如果你的代码有很多人在工作,你的函数是不平凡的,那么你应该标记“const”,你可以做任何事情。 在写行业代码的时候,你应该总是假设你的同事是精神病患者,试图让你得到任何可能的方式(尤其是因为它往往是你自己的未来)。

另外,正如前面提到的那样,这可能会帮助编译器优化一些东西(尽管这是一个很长的一步)。




有时(太频繁!)我必须解开别人的C ++代码。 我们都知道别人的 C ++代码几乎是按照定义完成的:)所以我做的第一件事情就是在每一个变量定义中都将const放入const ,直到编译器开始吠叫。 这也意味着const值限定的参数,因为它们只是调用者初始化的局部变量。

嗯,我希望变量默认是const ,非常量变量需要mutable




以下两行在功能上是等同的:

int foo (int a);
int foo (const int a);

很明显,如果第二种方法被定义的话,你将无法修改foo的内容,但是与外部没有任何区别。

const真正派上用场的地方是使用引用或指针参数:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

这说的是,foo可以采取一个大的参数,也许是一个千兆字节大小的数据结构,而不需要复制它。 另外,它对调用者说,“Foo不会*改变该参数的内容”。 传递const引用也允许编译器做出某些性能决定。

*:除非它消除了常量,但这是另一个帖子。




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

在代码中添加额外多余的const值以传递值为参数的内部类型参数混淆了您的API,但对调用方或API用户没有任何有意义的承诺(这只会妨碍实现)。

在不需要API的时候太多的“const”就像是“ 哭闹的狼 ”,最终人们会开始忽视“const”,因为它到处都是,而且大部分时间都没有意义。

API中额外const的“reductio ad absurdum”参数对于前两个点是好的,如果更多的const参数是好的,那么每个可以有const的参数都应该有一个const。 事实上,如果真的那么好,你会希望const成为参数的默认值,并且只有当你想改变参数时才有一个像“mutable”这样的关键字。

所以,让我们尽可能地尝试把常量放进去:

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

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

考虑上面的代码行。 不仅声明更加混乱,阅读时间越来越长,而且四个“常量”关键字中的三个可以被API用户安全地忽略。 但是,额外使用'const'使第二行可能是危险的!

为什么?

第一个参数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放在定义中。

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

然而,相反是真实的......你可以只在声明中放置一个伪造的const,并在定义中忽略它。 这只会使API中多余的const成为一个可怕的谎言 - 看到这个例子:

class 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应该是C ++中的默认值。 喜欢这个 :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable



当我为C ++编写代码时,我把所有可能的东西都放在一边。 使用const是帮助编译器帮助你的好方法。 例如,强制你的方法返回值可以让你免于错别字,例如:

foo() = 42

当你的意思是:

foo() == 42

如果foo()被定义为返回一个非const引用:

int& foo() { /* ... */ }

编译器会很高兴让你给函数调用返回的匿名临时值赋值。 使其不变:

const int& foo() { /* ... */ }

消除这种可能性。




关于comp.lang.c ++的旧文章“Guru of the Week”,在这里有一个很好的讨论。

相应的GOTW文章可以在Herb Sutter的网站上找到




我使用const作为引用(或指针)的函数参数,它们只是[in]数据,不会被函数修改。 意思是,当使用引用的目的是为了避免复制数据,而不允许改变传递的参数。

在你的例子中,将const放在布尔b参数上只会对实现施加一个约束,并不会影响类的接口(尽管通常不建议更改参数)。

的函数签名

void foo(int a);

void foo(const int a);

是一样的,这解释了你的.c和.h

阿萨夫




我说const你的价值参数。

考虑这个越野车功能:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

如果数字参数是const,编译器会停止并警告我们这个错误。




如果你使用->*.*运算符,这是必须的。

它阻止你写类似的东西

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

我现在差不多做了,可能不会做你想要的。

我想说的是

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

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




啊,一个艰难的。 一方面,一个声明是一个合约,并且按值传递一个const参数是没有意义的。 另一方面,如果你看一下函数的实现,那么如果你声明一个参数常量,就给了编译器更多的优化机会。




因为你不会修改调用者的对象,所以const通过值传递时是没有意义的。

除非函数的目的是修改传递的值,否则在通过引用传递时,const应该是首选。

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

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

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

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

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




标记值参数'const'绝对是一个主观的东西。

不过,我更喜欢标记值参数const,就像你的例子。

void func(const int n, const long l) { /* ... */ }

对我的价值是清楚地表明函数参数值永远不会被函数改变。 它们在开始和结束时会有相同的价值。 对我来说,这是保持一种非常实用的编程风格的一部分。

对于一个简短的函数来说,在'const'那里可以浪费时间/空间,因为通常这个函数并没有修改参数。

但是对于一个更大的函数,它是一种实现文档的形式,并且由编译器执行。

我可以肯定,如果我用'n'和'l'做一些计算,我可以重构/移动这个计算,而不用担心会得到不同的结果,因为我错过了一个或两个都改变了的地方。

由于它是一个实现细节,因此不需要在头中声明值参数const,就像不需要声明与实现使用的名称相同的函数参数一样。




在你提到的情况下,它不会影响你的API的调用者,这就是为什么它不常用(在头文件中是不必要的)。 它只影响你的功能的执行。

这不是一件特别糟糕的事情,但是它的好处并不是很大,因为它不会影响你的API,并且增加了输入,所以通常不会这样做。




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

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

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




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

据我所知,编译器可能会很好地看到一个const值参数,并说:“嘿,这个函数不会修改它,所以我可以通过引用,并保存一些时钟周期。 我不认为它会做这样的事情,因为它改变了功能签名,但它是重点。 也许它做了一些不同的堆栈操作或什么...重点是,我不知道,但我知道试图比编译器更聪明只会导致我被羞辱。

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




我使用const我可以。 参数的Const意味着它们不应该改变它们的值。 通过参考时,这是特别有价值的。 const函数声明函数不应该改变类成员。




可能是这不会是一个有效的论点。 但是如果我们在一个函数内增加一个const变量的值,编译器会给我们一个错误:“ error:只读参数的增量 ”。 所以这意味着我们可以使用const关键字来防止意外地修改函数内部的变量(我们不应该是/只读的)。 所以如果我们在编译的时候不小心做了这个,编译器会让我们知道。 如果你不是唯一一个从事这个项目的人,这一点特别重要。




Const参数只有在参数传递时才有用,例如引用或指针。 当编译器看到一个const参数时,它确保参数中使用的变量在函数体内不被修改。 为什么会有人想要一个按值参数保持不变? :-)




如果参数是通过值(而不是参考)传递的,那么参数是否被声明为const(除非它包含引用成员 - 对于内置类型不是问题),通常没有太大区别。 如果参数是一个引用或指针,通常最好保护被引用/指向的内存,而不是指针本身(我认为你不能把引用本身设为const,不是那么重要,因为你不能改变裁判) 。 保护你所能做的一切似乎是个好主意。 如果参数只是POD(包括内置类型),并且没有机会沿着道路进一步改变(例如在您的示例中为bool参数),则可以忽略它。

我不知道.h / .cpp文件声明的区别,但它确实是有道理的。 在机器代码级别,没有什么是“const”,所以如果你声明一个函数(在.h中)作为非const,那么代码就像你把它声明为const(抛开优化)一样。 但是,它可以帮助您使编译器不会在函数(.ccp)的实现中更改变量的值。 当你从一个允许改变的接口继承的情况下,它可能会派上用场,但是你不需要改变参数来实现所需的功能。







总结:

  • “通常情况下,传递价值是毫无用处和误导的。” 从GOTW006
  • 但是,您可以像在变量中一样将它们添加到.cpp中。
  • 请注意,标准库不使用const。 例如std::vector::at(size_type pos) 。 标准库的好处对我有好处。



我不会把const放在这样的参数上 - 每个人都知道一个布尔值(而不是布尔值)是不变的,所以加入它会让人们想“等等,什么? 甚至你通过引用传递参数。




用const记住的事情是,从一开始就把事情变得更加容易,而不是稍后再尝试。

当你想要某些东西不变的时候使用const - 它是一个附加的提示,它描述了你的函数做了什么以及期望什么。 我见过很多C API,可以处理其中的一些,特别是接受C字符串的C API!

我更倾向于忽略cpp文件中的const关键字,而不是标题,但是由于我倾向于剪切+粘贴它们,所以它们会保存在两个地方。 我不知道为什么编译器允许,我猜它是一个编译器的东西。 最好的做法是把你的const关键字放在两个文件中。




真的没有理由做一个值参数“const”,因为函数只能修改变量的一个副本。

使用“const”的原因是,如果你通过引用传递更大的东西(例如一个有许多成员的结构),在这种情况下,它确保函数不能修改它; 或者说,如果你试图用传统的方式修改它,编译器会抱怨。 它防止它被意外修改。




由于参数是按值传递的,所以如果你从调用函数的角度来指定const或者没有任何区别的话,那么基本上没有任何意义的将值传递给const的参数。




在你的例子所有consts没有什么目的。C ++是通过按值默认,所以函数获得的那些整数和布尔值的副本。即使该函数修改它们,呼叫者的副本不会受到影响。

所以我想避免额外consts因为

  • 他们redudant
  • 他们弄乱文本
  • 他们阻止我改变传递值情况下,它可能是有用的或有效的。



我知道这个问题是“有点”过时但我来到翻过其别人也可能在将来这样做......我仍然怀疑这可怜的家伙会列出这里阅读我的评论:)

在我看来,我们还是太局限思维的C风格的方式。在OOP PARADIGMA我们玩弄的对象,而不是类型。const对象可以是从非const对象概念上不同的,特别是在逻辑const的(与按位常数)的意义。因此,即使功能则params的const正确性是(可能)的过细致在荚果的情况下,它不是那么在对象的情况下。如果一个函数与一个const对象的工作就应该这么说。考虑下面的代码片段

#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;

}

PS:你可能会说,(常量)引用将在这里更合适,并为您提供了相同的行为。恩,对。只是让从我能看到其他地方不同的画面...




作为一个VB.NET程序员需要使用C ++程序与50+公开的函数,并且偶尔使用const限定符.h文件时,难以知道什么时候访问使用的ByRef或BYVAL的变量。

当然,程序会告诉你通过你在哪里犯了错行产生异常错误,但你需要猜测其中2-10参数是错误的。

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

我写了解析.h文件,并创建所有声明函数命令的awk脚本,但没有一个指标哪个变量R / O对R / W,它只做了一半工作。

编辑:

在我加入以下其他用户的鼓励;

下面是形成不良的条目.H一个(IMO)的一个例子;

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

注意第一个参数丢失的“常量”。没有它,一个程序(或其他开发商)不知道的第一个参数应通过“BYVAL。” 通过添加“常量”它使.h文件自我记录,以便使用其他语言的开发人员可以很容易地编写工作代码。