为什么我不能在另一个函数里面定义一个函数?
这不是一个lambda函数的问题,我知道我可以分配一个lambda到一个variables。
允许我们声明,但不在代码中定义一个函数有什么意义?
例如:
#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; }
所以我想知道的是为什么C ++允许two
似乎是无用的, three
似乎更复杂,但不允许one
?
编辑:
从答案看来,代码内声明可能能够防止命名空间污染,但是我希望听到的是为什么声明函数的能力已被允许,但是定义函数的能力已被禁止。
为什么不允许; N0295很早就提出了嵌套function,其中说:
我们讨论将嵌套函数引入到C ++中。 嵌套函数很好理解,编译器厂商,程序员或者委员会的介绍只需要很less的努力。 嵌套function提供显着的优势,
显然,这个build议被拒绝了,但由于我们在1993
没有在线提供会议纪要,所以我们没有可能的理由来提供这个拒绝。
实际上,这个提议在Lambdaexpression式和C ++的闭包中被logging为可能的替代scheme:
一篇文章[Bre88]和C ++委员会[SH93]的build议N0295build议向C ++添加嵌套函数。 嵌套函数与lambdaexpression式类似,但在函数体中定义为语句,除非该函数处于活动状态,否则不能使用结果闭包。 这些build议也不包括为每个lambdaexpression式添加一个新types,而是像普通函数一样实现它们,包括允许一种特殊types的函数指针来引用它们。 这两个build议都早于向C ++添加模板,所以不要提到将嵌套函数与genericsalgorithm结合使用。 而且,这些提议没有办法将局部variables复制到闭包中,因此它们所产生的嵌套函数在它们的封闭函数之外是完全不可用的
考虑到我们现在有lambda函数,我们不太可能看到嵌套的函数,因为正如本文所概述的,它们是同一个问题的替代scheme,嵌套函数相对于lambda函数有一些限制。
至于你的问题的这一部分:
// This is legal, but why would I want this? int two(int bar);
有些情况下,这将是一个有用的方式来调用你想要的function。 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) }
那么答案是“历史原因”。 在C中,你可以在块范围内有函数声明,而C ++devise者没有看到删除这个选项的好处。
一个示例用法是:
#include <iostream> int main() { int func(); func(); } int func() { std::cout << "Hello\n"; }
海事组织这是一个坏主意,因为提供一个不符合函数实际定义的声明很容易犯一个错误,导致未定义的行为,不会被编译器诊断。
在你给的例子中, void two(int)
被声明为一个外部函数,该声明只在main
函数的范围内有效 。
如果您只希望在main()
提供two
名称,以避免污染当前编译单元中的全局名称空间,这是合理的。
回应评论的示例:
main.cpp中:
int main() { int foo(); return foo(); }
Foo.cpp中:
int foo() { return 0; }
不需要头文件。 编译和链接
c++ main.cpp foo.cpp
它会编译并运行,程序将按预期返回0。
你可以做这些事情,主要是因为他们其实并不是那么难。
从编译器的angular度来看,在另一个函数内部有一个函数声明是非常简单的。 编译器需要一种机制来允许函数内部的声明来处理函数内部的其他声明(例如, int x;
)。
它通常具有parsing声明的一般机制。 对于编写编译器的人来说,在parsing另一个函数内部或外部的代码时是否调用这个机制并不重要 – 这只是一个声明,所以当它看到足够的知道什么是声明的时候,它调用处理声明的编译器部分。
事实上,在函数内部禁止这些特定的声明可能会增加额外的复杂性,因为编译器需要完全无偿的检查,看它是否已经在函数定义内部查看代码,并根据这些代码决定是允许还是禁止这个特定宣言。
这留下了嵌套函数是如何不同的问题。 嵌套函数由于它如何影响代码生成而不同。 在允许嵌套函数的语言中(例如,Pascal),通常期望嵌套函数中的代码可以直接访问嵌套函数的variables。 例如:
int foo() { int x; int bar() { x = 1; // Should assign to the `x` defined in `foo`. } }
没有本地函数,访问局部variables的代码相当简单。 在典型的实现中,当执行进入函数时,局部variables的一些空间块被分配到堆栈上。 所有的局部variables都被分配在这个单独的块中,每个variables都被视为一个从块开始(或结束)开始的偏移量。 例如,让我们考虑一个像这样的函数:
int f() { int x; int y; x = 1; y = x; return y; }
一个编译器(假设它没有优化掉额外的代码)可能会产生这样的代码:
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);
特别是,它有一个位置指向局部variables块的开始,并且对局部variables的所有访问都是与该位置的偏移量。
嵌套函数不再是这种情况 – 相反,函数不仅可以访问自己的局部variables,还可以访问所有嵌套函数的本地variables。 而不是只有一个“stack_pointer”来计算偏移量,它需要走回堆栈来find它所嵌套函数的本地stack_pointers。
现在,在一个并不是所有可怕的情况下 – 如果bar
嵌套在foo
,那么bar
就可以在前面的堆栈指针中查找栈来访问foo
的variables。 对?
错误! 那么有些情况可能是正确的,但并非一定如此。 特别是, bar
可以是recursion的,在这种情况下,给定的bar
调用可能需要查看一些接近任意数量的级别来备份堆栈以查找周围函数的variables。 一般来说,你需要做以下两件事之一:要么在堆栈上放置一些额外的数据,所以它可以在运行时search堆栈来find它周围的函数的堆栈帧,否则你会有效地传递一个指针周围函数的栈帧作为嵌套函数的隐藏参数。 哦,但是不一定只有一个周围的函数 – 如果你可以嵌套函数,你可以任意嵌套它们(或多或less),所以你需要准备好传递任意数量的隐藏参数。 这意味着你通常最终会得到一些类似堆栈框架的链表到周围的函数,访问周围函数的variables是通过遍历链表来find它的堆栈指针,然后访问堆栈指针的偏移量。
然而,这意味着访问“本地”variables可能不是一件小事。 find正确的堆栈框架来访问variables可能是不平凡的,所以访问周围函数的variables也是(至less通常)比访问真正的局部variables慢。 当然,编译器必须生成代码来查找正确的堆栈帧,通过任意数量的堆栈帧来访问variables,等等。
这是C通过禁止嵌套函数而避免的复杂性。 现在,当前的C ++编译器与1970年代的C编译器是一个非常不同的东西。 在多重虚拟inheritance的情况下,C ++编译器在任何情况下都必须处理同样一般性质的事情(即在这种情况下查找基类variables的位置也可能不重要)。 在一个百分比的基础上,支持嵌套函数不会给当前的C ++编译器增加很多复杂性(有些如gcc已经支持它们)。
同时,它也很less增加太多实用性。 特别是,如果你想定义一个在函数内部像函数一样的东西,你可以使用lambdaexpression式。 这实际上创build了一个对象(即某个类的一个实例),它重载了函数调用操作符( operator()
),但它仍然提供类似于函数的function。 它使得捕获(或不)来自周围环境的数据更加明确,这使得它可以使用现有的机制,而不是发明一个全新的机制和一套使用规则。
底线:即使它最初可能看起来像嵌套的声明是硬的,嵌套的函数是微不足道的,或多或less的情况正好相反:嵌套函数实际上比嵌套声明要复杂得多。
第一个是函数定义,不允许。 很明显,wt是将函数的定义放在另一个函数中的用法。
但其他两个只是声明。 想象一下你需要使用int two(int bar);
主要方法里面的函数。 但是它在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; }
这个语言特性是从Cinheritance而来的,C语言在早期就有了一些function(函数声明的范围可能?) 。 我不知道现在的C程序员是否使用了这个特性,我真的怀疑它。
那么,总结一下答案:
在现代 C ++(我知道,至less)中没有这个特性的目的,因为C ++到C的向后兼容性(我想:))。
感谢下面的评论:
函数原型的作用范围是它声明的函数,所以可以有一个更整洁的全局名称空间 – 通过引用没有#include
外部函数/符号。
其实,有一个用例是可以想象的有用的。 如果你想确保某个函数被调用(并且你的代码被编译),不pipe周围的代码是什么声明的,你都可以打开自己的代码块并在其中声明函数原型。 (灵感来自Johannes Schaub, https ://stackoverflow.com/a/929902/3150802,通过TeKa, https: //stackoverflow.com/a/8821992/3150802)。
如果你必须包含你不控制的头文件,或者你有一个可能在未知代码中使用的多行macros,这可能特别有用。
关键在于本地声明取代了最内层封闭块中的先前声明。 虽然这可以引入微妙的错误(我认为,在C#中是被禁止的),但它可以被有意识地使用。 考虑:
// somebody's header void f(); // your code { int i; int f(); // your different f()! i = f(); // ... }
链接可能是有趣的,因为机会是属于一个库的标题,但我想你可以调整链接器参数,以便在考虑到库时f()
被parsing到你的函数。 或者你告诉它忽略重复的符号。 或者你不要链接到图书馆。
这不是OP问题的答案,而是对几个评论的回复。
我在评论和答复中不同意这些观点: 1嵌套声明据说是无害的, 2嵌套定义是无用的。
1所谓的嵌套函数声明的无害性的主要反例是臭名昭着的最烦恼parsing(Most Vexing Parse) 。 国际海事组织所造成的混淆蔓延就足以保证一个额外的规则禁止嵌套声明。
2对嵌套函数定义的无用性的第一个反例经常需要在一个函数中的几个地方执行相同的操作。 有一个明显的解决方法:
private: inline void bar(int abc) { // Do the repeating operation } public: void foo() { int a, b, c; bar(a); bar(b); bar(c); }
但是,这个解决scheme往往足以污染具有众多私有函数的类定义,每个函数恰好用于一个调用者。 嵌套的函数声明会更干净。
具体回答这个问题:
从答案看来,代码内声明可能能够防止命名空间污染,但是我希望听到的是为什么声明函数的能力已被允许,但是定义函数的能力已被禁止。
因为考虑这个代码:
int main() { int foo() { // Do something return 0; } return 0; }
语言devise者的问题:
-
foo()
是否可用于其他函数? - 如果是这样,它的名字是什么?
int main(void)::foo()
? - (请注意,C中不可能有2个C ++的鼻祖)
- 如果我们想要一个本地函数,我们已经有了一个方法 – 使它成为本地定义的类的静态成员。 那么我们应该添加另一个达到相同结果的句法吗? 为什么这样做? 这不会增加C ++编译器开发人员的维护负担吗?
- 等等…
只是想指出GCC编译器允许你在函数内部声明函数。 在这里阅读更多关于它。 此外,随着lambdas引入C ++,这个问题现在已经过时了。
在其他函数中声明函数头的能力,在下面的例子中我觉得很有用:
void do_something(int&); int main() { int my_number = 10 * 10 * 10; do_something(my_number); return 0; } void do_something(int& num) { void do_something_helper(int&); // declare helper here do_something_helper(num); // Do something else } void do_something_helper(int& num) { num += std::abs(num - 1337); }
我们有什么在这里? 基本上,你有一个应该从main调用的函数,所以你要做的就是像forward一样声明它。 但是后来你意识到,这个函数还需要另一个function来帮助它完成它所做的事情。 因此,不是在main之上声明辅助函数,而是在需要它的函数中声明它,然后才可以从该函数调用该函数,而只能使用该函数。
我的意思是,在函数内声明函数头可以是函数封装的一个间接方法,它允许一个函数通过委托给其他一些只知道它的函数来隐藏它所做的某些部分,几乎给出了一个嵌套的错觉function 。
嵌套函数声明可能允许为1.前向引用2.能够声明指向函数的指针并在有限范围内传递其他函数。
嵌套函数定义是不允许的,可能是因为1.优化2.recursion(封闭和嵌套定义的函数)3.重入4.并发和其他multithreading访问问题。
从我有限的理解:)