本地函数声明有什么用处吗?

23

大多数像我这样的C++程序员在某个时刻都犯过以下错误:

class C { /*...*/ };

int main() {
  C c();     // declares a function c taking no arguments returning a C,
             // not, as intended by most, an object c of type C initialized
             // using the default constructor.
  c.foo();   // compiler complains here.

  //...
}

现在当你知道错误后,它似乎非常显而易见。但我想知道除了你可以这样做之外,是否有任何明智的用途来声明这种本地函数 - 特别是因为没有办法在同一块中定义这样的本地函数; 你必须在其他地方定义它。

我认为Java风格的本地类是一个相当不错的功能,我经常使用它们,特别是匿名类型。即使是本地C++类(可以具有内联定义的成员函数)也有一些用处。但是这种未定义的本地函数声明对我来说似乎非常尴尬。它只是C语言遗留问题吗?还是有某些更深层次的用例我不知道?

编辑:对于那些不信的人:C c()不是函数指针声明。

这个程序

int main()
{
  void g();
  cout << "Hello ";
  g();
  return 0;
}

void g()
{
  cout << "world." << endl;
}

输出 Hello world. 这个程序。

void fun()
{
  cout << "world." << endl;
}

int main()
{
  void g();
  g = fun;
  cout << "Hello ";
  g();
  return 0;
}

编译不通过。gcc 报错:

error: cannot convert 'void ()()' to 'void ()()' in assignment

Comeau 报错:

error: expression must be a modifiable lvalue

请注意,GCC错误信息是错误的(已知的错误)。它应该输出“error: cannot convert 'void ()' to 'void ()' in assignment”,但实际上它错误地解析了类型标识符(type-id)。 (它应该说“void()”,而不是返回函数的函数(非法类型)的函数“void()()”) - Johannes Schaub - litb
相关问题:https://dev59.com/1EfSa4cB1Zd3GeqPAMtY - Johannes Schaub - litb
我不确定gcc错误消息是否是一个bug,实际上你可以用额外的一对括号void(g)()声明函数,当处理函数指针时需要使用它:void * g() 是返回 void* 的函数,而 void(* g)() 是指向类型为void()()的函数的指针,即不带参数且不返回任何内容的函数。 - Tobias
@Tobias,就是这个问题。虽然void(a)();是允许的,但如果您删除标识符,则必须删除多余的括号,因为它将被视为函数声明符。如果考虑到派生,就会变得清晰:函数:()返回函数:()(),返回void: void()()这是一种无效类型。您可以添加用于绑定运算符的括号。就像您可以说:函数:()返回函数:(())()返回void:void(())()。或指针:,到函数:()(),返回void:void(*)()(当然,这是有效的)。 - Johannes Schaub - litb
请随意告诉他们以便修复:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36002 - Johannes Schaub - litb
7个回答

13

我唯一能想到的用途是减少函数声明的作用域:

int main()
{
    void doSomething();
    doSomething();
    return 0;
}

void otherFunc()
{
    doSomething();  // ERROR, doSomething() not in scope
}

void doSomething()
{
    ...
}
当然,有更好的解决方案。如果您需要隐藏一个函数,您应该通过将函数移动到单独的模块中来重构代码,以便需要调用您想要隐藏的函数的功能都在同一个模块中。然后,您可以通过声明为静态(C)或将其放置在匿名命名空间中(C++方式)使该函数成为模块本地。

2

正如這個答案中的第三個片段所說,它可以幫助處理作用域遮蔽。

#include <stdio.h>

void c(); // Prototype of a function named ``c''

int main() {

    c(); // call the function

    { // additional scoppe needed for C, not for C++
        int c = 0; // shadow the c function with a c integer variable
        c++;
        {
            void c(); // hide the c integer variable back with the c function
            c();
        }
        ++c;
    } //!

    return 0;
}

void c() {
    printf("Called\n");
}

我认为这种用法不太可能经常有用,并且有可能在任何设计良好的程序中都不会出现。

我仍然认为这是该功能最可能的原因,晚些时候定义函数就像变量一样并不合适。


2

这是一个前向声明的原型。如果你有很多本地函数,或者本地函数按照定义顺序相反互相调用,那么你可能需要它。


我不理解你在这里的观点:在你的用例中,我只会在源文件的开头(甚至是某个私有头文件中)声明所有函数,然后愉快地实现它们。为什么我需要在一个函数内部声明另一个函数呢? - Tobias

2

我认为唯一合理的使用场景是,允许编译单元中的一个函数知道另一个编译单元中定义的函数。这种情况下可以考虑使用,但我认为这已经有些过度了。


但是为什么要在另一个函数的定义内部声明一个函数(而不是在函数定义外部或头文件中声明)? - Tobias
1
就像我说的那样,只有该函数才能看到它。 - Rob K

2

当我想将本地函数声明作为参数传递给其他函数时,我希望C语言也能支持这个特性。在其他语言中,我经常这样做的原因是为了封装数据结构的实现。

例如,我定义了一些数据结构,比如树或图,我不想暴露其内部实现的细节,因为我可能想要更改或变化它。因此,我会公开访问器和修改器函数以及遍历函数来迭代元素。遍历函数的参数是一个操作元素的函数;遍历函数的任务是对每个元素执行参数函数,并可能以某种方式汇总结果。现在,当我调用遍历函数时,参数函数通常是依赖于本地状态的一些专门操作,因此应该定义为本地函数。将包含本地状态的变量的函数推到外部成为全局函数,或者将其放入专门创建的内部类中,都很丑陋。但这是我在C和Java中遇到的问题,而不是C++。


好的,你是说这是C语言的遗留问题,在C ++中已经失去了它的作用? - Tobias
并不完全是这样。我实际上对C++并不够熟练,无论是否使用模板,都无法确定我通常会如何处理这种情况。迭代器在C++中很常见,但关于接受函数作为参数的遍历方法,我不太确定。 - reinierpost
更新:请参见https://dev59.com/kkvSa4cB1Zd3GeqPe30e。 - reinierpost
2
如果本地函数声明是非法的,那么我需要做的就是这样的(示例代码会更好):在这里使用本地声明。 - Jon

1

有时候我这么做是出于与我们被鼓励在第一次使用变量之前(而不是之前)声明变量的同样原因,即为了提高可读性。(是的,我意识到对于变量来说更重要,因为它使您免于在您认为的第一次使用之前检查变量是否已使用)。将原型(特别是如果它比c()更复杂)靠近函数调用可以提高可读性。特殊情况C c()对人类来说具有误导性是不幸的,但是在本地声明函数的一般想法是有价值的。

当然,对于已经在作用域中的函数名称也是如此。为什么不在每次使用之前重新声明每个函数?我的答案是:适度处理所有事情。过多的杂乱会影响可读性(以及可维护性-如果函数签名发生变化)。因此,虽然我不会制定规则,但有时在本地声明函数很有用。


你有做过吗?你认识有人做过吗?我不认识,如果你能提供一些真实世界的例子就太好了。这将非常有趣。 - Tobias
我有一点模糊的记忆,好像确实做过一两次,但我不记得具体的情况了。问题是我调用的大多数函数都是类方法,所以这种情况相当罕见。 - Ari

-1
如果你想区分声明不带参数的函数和实例化具有默认析构函数的类,请省略括号。
class C
{
  public:
    void foo() {}
};


int main()
{
    // declare function d, taking no arguments, returning fresh C
    C d();

    // instantiate class (leave parenthesis out)
    C c;
    c.foo();

    // call declared function
    C goo = d();

    return 0;
}

C d()
{
    C c;
    // ..
    return c;
}

1
我的问题不是如何实例化一个对象,而是,你为什么要以这种方式声明函数d(与在main之外声明它相比)? - Tobias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接