为什么我不能在另一个函数中定义一个函数?

Jon*_*Mee 78 c++ functor function-declaration

这不是lambda函数问题,我知道我可以将lambda赋给变量.

允许我们声明,但不在代码中定义函数是什么意思?

例如:

#include <iostream>

int main()
{
    // This is illegal
    // int one(int bar) { return 13 + bar; }

    // This is legal, but why would I want this?
    int two(int bar);

    // This gets the job done but man it's complicated
    class three{
        int m_iBar;
    public:
        three(int bar):m_iBar(13 + bar){}
        operator int(){return m_iBar;}
    }; 

    std::cout << three(42) << '\n';
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

所以我想知道的是为什么C++会允许two哪些看似无用,three哪个看起来更复杂,但却不允许one

编辑:

从答案中可以看出,代码内声明可能能够防止命名空间污染,我希望听到的是为什么声明函数的能力已被允许,但是不允许定义函数的能力.

Sha*_*our 40

不明白为什么one不被允许; 很久以前在N0295中提出了嵌套函数,其中说:

我们讨论将嵌套函数引入C++.嵌套函数很好理解,它们的引入需要编译器供应商,程序员或委员会的很少努力.嵌套功能提供了显着的优势,[...]

显然这个提议被拒绝了,但由于我们没有在线会议记录,因此1993我们没有可能获得拒绝的理由.

事实上,这个提议在Lambda表达式和C++的闭包中被注意到,作为一种可能的替代方案:

一篇文章[Bre88]和C++委员会[SH93]的提案N0295建议将嵌套函数添加到C++中.嵌套函数类似于lambda表达式,但是被定义为函数体内的语句,除非该函数处于活动状态,否则不能使用生成的闭包.这些提议也不包括为每个lambda表达式添加新类型,而是更像普通函数实现它们,包括允许一种特殊的函数指针来引用它们.这两个提议都早于向C++添加模板,因此不要提及嵌套函数与通用算法的结合使用.此外,这些提议无法将局部变量复制到闭包中,因此它们生成的嵌套函数在其封闭函数之外完全无法使用

考虑到我们现在有lambda,我们不太可能看到嵌套函数,因为正如本文所概述的那样,它们是同一问题的替代品,嵌套函数相对于lambdas有几个局限性.

至于你的这部分问题:

// This is legal, but why would I want this?
int two(int bar);
Run Code Online (Sandbox Code Playgroud)

在某些情况下,这将是调用所需函数的有用方法.草案C++标准部分3.4.1 [basic.lookup.unqual]为我们提供了一个有趣的例子:

namespace NS {
    class T { };
    void f(T);
    void g(T, int);
}

NS::T parm;
void g(NS::T, float);

int main() {
    f(parm); // OK: calls NS::f
    extern void g(NS::T, float);
    g(parm, 1); // OK: calls g(NS::T, float)
}
Run Code Online (Sandbox Code Playgroud)


M.M*_*M.M 31

嗯,答案是"历史原因".在C中,您可以在块作用域中具有函数声明,并且C++设计者没有看到删除该选项的好处.

一个示例用法是:

#include <iostream>

int main()
{
    int func();
    func();
}

int func()
{
    std::cout << "Hello\n";
}
Run Code Online (Sandbox Code Playgroud)

IMO这是一个坏主意,因为通过提供与函数的真实定义不匹配的声明很容易出错,从而导致编译器无法诊断的未定义行为.

  • "这通常被认为是一个坏主意' - 需要引用. (10认同)
  • 我想你所说的是将函数声明放在头文件中的常见做法通常很有用.我认为没有人会不同意这一点.我认为没有理由的是在功能范围内声明外部函数的断言"通常被认为是一个坏主意". (6认同)
  • @RichardHodges:嗯,函数声明属于头文件,以及.c或.cpp文件中的实现,因此在函数定义中包含这些声明会违反这两个指南中的任何一个. (4认同)
  • 它如何防止声明与定义不同? (2认同)

Ric*_*ges 23

在您给出的示例中,void two(int)被声明为外部函数,该声明仅在main函数范围内有效.

这是合理的,如果你只是想使名称two中可用main(),以避免污染当前编译单元中的全局命名空间.

回复评论的示例:

main.cpp中:

int main() {
  int foo();
  return foo();
}
Run Code Online (Sandbox Code Playgroud)

Foo.cpp中:

int foo() {
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

不需要头文件.编译和链接

c++ main.cpp foo.cpp 
Run Code Online (Sandbox Code Playgroud)

它将编译并运行,程序将按预期返回0.

  • @JonathanMee标题没有什么特别之处.它们只是放置声明的便利位置.函数内的声明与标题中的声明一样有效.所以,不,你不需要包含你要链接的标题(甚至可能根本没有标题). (5认同)

Jer*_*fin 18

你可以做这些事情,主要是因为他们实际上并不是那么难做.

从编译器的角度来看,在另一个函数中包含函数声明实现起来非常简单.编译器需要一种机制来允许函数内部的声明来处理函数内部的其他声明(例如int x;).

它通常具有解析声明的通用机制.对于编写编译器的人来说,在解析另一个函数内部或外部的代码时是否调用该机制并不重要 - 它只是一个声明,所以当它看到足够知道什么是声明时,它调用处理声明的编译器部分.

实际上,禁止函数内部的这些特定声明可能会增加额外的复杂性,因为编译器需要进行完全无偿的检查以查看它是否已经在函数定义中查看代码并基于此决定是否允许或禁止此特定宣言.

这留下了嵌套函数如何不同的问题.嵌套函数因其影响代码生成的方式而有所不同.在允许嵌套函数(例如,Pascal)的语言中,您通常希望嵌套函数中的代码可以直接访问嵌套函数所在的函数的变量.例如:

int foo() { 
    int x;

    int bar() { 
        x = 1; // Should assign to the `x` defined in `foo`.
    }
}
Run Code Online (Sandbox Code Playgroud)

没有本地函数,访问局部变量的代码非常简单.在典型的实现中,当执行进入函数时,局部变量的一些空间块被分配在堆栈上.所有局部变量都在该单个块中分配,并且每个变量仅被视为与块的开头(或结束)的偏移量.例如,让我们考虑一个像这样的函数:

int f() { 
   int x;
   int y;
   x = 1;
   y = x;
   return y;
}
Run Code Online (Sandbox Code Playgroud)

编译器(假设它没有优化掉额外的代码)可能会生成大致相当于此的代码:

stack_pointer -= 2 * sizeof(int);      // allocate space for local variables
x_offset = 0;
y_offset = sizeof(int);

stack_pointer[x_offset] = 1;                           // x = 1;
stack_pointer[y_offset] = stack_pointer[x_offset];     // y = x;
return_location = stack_pointer[y_offset];             // return y;
stack_pointer += 2 * sizeof(int);
Run Code Online (Sandbox Code Playgroud)

特别是,它有一个位置指向局部变量块的开头,所有对局部变量的访问都是从该位置的偏移量.

对于嵌套函数,不再是这种情况 - 相反,函数不仅可以访问自己的局部变量,还可以访问嵌套所有函数的局部变量.它不是只有一个"stack_pointer"来计算偏移量,而是需要向上走回堆栈以找到它嵌套的函数本地的stack_pointers.

现在,在一个微不足道的情况下也不是那么可怕 - 如果bar嵌套在内部foo,那么bar只需在前一个堆栈指针处查找堆栈以访问foo变量.对?

错误!嗯,有些情况可能是真的,但事实并非如此.特别是,bar可以是递归的,在这种情况下,给定的调用bar可能必须看起来几乎任意数量的级别备份堆栈以找到周围函数的变量.一般来说,你需要做两件事之一:要么在堆栈上放一些额外的数据,所以它可以在运行时搜索堆栈以查找其周围函数的堆栈帧,否则你有效地传递指针周围函数的堆栈帧作为嵌套函数的隐藏参数.哦,但是不一定只有一个周围的函数 - 如果你可以嵌套函数,你可以将它们(或多或少)任意嵌套,所以你需要准备好传递任意数量的隐藏参数.这意味着您通常最终会得到类似于堆栈帧的链接列表到周围函数的内容,并且通过遍历链接列表来查找其堆栈指针,然后从该堆栈指针访问偏移量来访问周围函数的变量.

然而,这意味着访问"本地"变量可能不是一件小事.找到正确的堆栈帧来访问变量可能非常重要,因此访问周围函数的变量也(至少通常)比访问真正的局部变量更慢.当然,编译器必须生成代码以找到正确的堆栈帧,通过任意数量的堆栈帧访问变量,等等.

是C通过禁止嵌套函数而避免的复杂性.现在,当前的C++编译器与1970年代的老式C编译器完全不同.对于多重的虚拟继承之类的东西,C++编译器必须在任何情况下处理这种相同的一般性事物(即,在这种情况下找到基类变量的位置也可能是非平凡的).在百分比的基础上,支持嵌套函数不会给当前的C++编译器带来太多复杂性(有些像gcc已经支持它们).

同时,它很少增加很多实用性.特别是,如果你想定义的东西,行为像一个函数的函数里面,你可以使用lambda表达式.这实际上创建的是一个对象(即某个类的实例),它重载了函数调用operator(operator()),但它仍然提供了类似函数的功能.它使得从周围上下文中捕获(或不捕获)数据更加明确,这使得它可以使用现有机制,而不是发明一个全新的机制和一组规则供其使用.

底线:即使最初似乎嵌套声明很难,嵌套函数也很简单,但或多或​​少相反:嵌套函数实际上比嵌套声明要复杂得多.


ANj*_*aNA 5

第一个是函数定义,不允许使用.显而易见,wt是将函数的定义放在另一个函数中的用法.

但其他两个只是宣言.想象一下,您需要int two(int bar);在main方法中使用函数.但它定义在main()函数下面,因此函数内部的函数声明使您可以将该函数与声明一起使用.

这同样适用于第三种.函数内部的类声明允许您在函数内部使用类,而无需提供适当的标头或引用.

int main()
{
    // This is legal, but why would I want this?
    int two(int bar);

    //Call two
    int x = two(7);

    class three {
        int m_iBar;
        public:
            three(int bar):m_iBar(13 + bar) {}
            operator int() {return m_iBar;}
    };

    //Use class
    three *threeObj = new three();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 什么是“减速”?您是说“声明”吗? (2认同)