将'const'用于功能参数

妖精的绣舞 提交于 2020-02-26 14:00:20

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

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


#1楼

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

在代码中为值传递的内在类型参数添加多余的const会使您的API混乱,而对调用者或API用户没有任何有意义的承诺(这只会妨碍实现)。

当不需要时,API中太多的“ const”就像“ 哭泣的狼 ”,最终人们会开始忽略“ const”,因为它遍地都是,在大多数时候没有任何意义。

API中额外的const的“ reducio ad absurdum”参数对于前两点是好的,那就是如果更多的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'会使第二行潜在危险!

为什么?

对第一个参数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不为true,则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-happy的倡导者声称这是一件好事,因为它使您只能将const放在定义中。

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

但是,反之亦然。您可以仅在声明中添加伪常量,而在定义中忽略它。 这只会使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显得杂乱无章,令人讨厌的烦恼,浅薄而毫无意义的承诺,不必要的障碍,并偶尔会导致非常危险的错误。


#2楼

如果使用->*.*运算符,则必须这样做。

它阻止您编写类似

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

我现在几乎做了,而这可能并没有达到您的预期。

我想说的是

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

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


#3楼

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

我个人倾向于不使用const,除了引用和指针参数。 对于复制的对象,这并不重要,尽管它可以更安全,因为它表示函数中的意图。 这确实是一个判断电话。 我确实倾向于使用const_iterator,尽管在循环执行某些操作时,并且我不打算对其进行修改,因此,只要严格维护引用类型的const正确性,我就对每个人都有自己的猜测。


#4楼

我在作为引用(或指针)的函数参数上使用const,这些参数仅是[输入]数据,不会被函数修改。 意思是,当使用引用的目的是避免复制数据并且不允许更改传递的参数时。

在您的示例中,将const放在boolean b参数上只会对实现施加约束,并且不会为类的接口做出贡献(尽管通常建议不要更改参数)。

的功能签名

void foo(int a);

void foo(const int a);

一样,这说明了您的.c和.h

阿萨夫


#5楼

在您提到的情况下,它不会影响您的API的调用者,这就是为什么它不常用(并且在标头中没有必要)的原因。 它仅影响功能的实现。

这样做并不是一件坏事,但是考虑到它不会影响您的API并带来键入的好处,因此带来的好处并不是很大,因此通常不会这样做。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!