重载函数声明的顺序在 c++ 中重要吗?

Does the order of overloaded function declaration matter in c++?

本文关键字:c++ 函数 声明 顺序 重载      更新时间:2023-10-16

我目前定义了一个多次重载的函数。对于某些重载定义,它使用不同类型的输入调用相同的函数。因此,函数 Foo 被定义为采用类型 A,但在函数主体中,它在类型 B 上调用 Foo。但是,类型 B 上的 Foo 是在定义 A 之后定义的。

我目前在编译时遇到错误,我认为这是由于重载定义的排序。我没有明确的错误消息或调试工具,所以我想知道上述情况是否确实会导致错误。

void Foo (A input) {
B b = B();
Foo(b);
}
void Foo (B input) {
printf("%s", input.toString());
}
int main() {
A a = A();
Foo(a);
return 0;
}
//code has been oversimplified

我认为这个问题可以归结为"编译器是只检查函数是否已定义,还是检查函数是否已在特定输入上定义?

重载的声明顺序无关紧要,因为以下内容是等效的:

// 1.
void foo(int);
void foo(double);
foo(42);
// 2.
void foo(double);
void foo(int);
foo(42);

重载的声明顺序确实很重要,因为以下内容并不等效:

// 3.
void foo(int);
foo(42);
void foo(double);
// 4.
void foo(double);
foo(42);
void foo(int);

简洁:只有在函数调用之前声明的函数才会参与重载解析。


在您的示例程序中,Foo(A)要么具有无限递归(如果B隐式转换为A),要么程序格式不正确,因为您尚未在调用之前声明Foo(B)

编译器是否只检查是否已定义函数

通常,编译器根本不检查是否已定义函数。但是,必须先声明函数,然后才能调用它。

简短的回答是肯定的 - 顺序很重要。 (此外,它实际上与重载无关 - 您可以将Foo(B)重命名为Goo(B)

针对您的特定问题的一种常见补救措施是转发声明Foo(B)

// Forward declaration
void Foo(B);
void Foo (A input) {
B b = B();
Foo(b);     // Compiler now knows about Foo(B), so this is fine.
}
void Foo (B input) {
// ...
}

编译器需要知道该特定函数 - 它必须在使用之前声明。 但是,它可以在其他地方定义。 在链接时,收集所有编译器输出并将符号"链接"在一起 - 链接器将弄清楚如何生成正确的指令以从该行调用Foo(B),或者可能内联它,等等。

有时可能需要向前声明函数。 例如

void Foo() {
if (condition) Goo();
}
void Goo() {
if (condition) Foo();
}

FooGoo都需要相互了解,所以你可以在Foo()的定义之前声明两者(或者如果合适,将它们放在标题中)。

重载函数声明的顺序在 c++ 中重要吗?

简短的回答:是的。C++的顺序很重要。举个例子:

i = 45;
int i;

这会导致错误(当然,假设在更高的范围内没有其他i)。无论是变量、函数、类还是其他东西都无关紧要;在C++符号在使用之前必须声明。即使它是一个重载函数,您在定义中使用的任何重载都必须排在第一位。

一个巧妙的技巧

虽然必须在使用函数之前声明函数,但不必在使用函数之前定义函数。我相信一个例子会有所帮助:

void Foo (A input);
void Foo (B input);

这些是函数声明。请注意,它缺少一个定义,即实现。这只是告诉编译器存在这样的函数。它还不需要知道它做了什么,只需要知道它在那里。

这对我们有什么帮助?好吧,考虑以下程序(顺便说一句,它有效):

void Foo (A input);
void Foo (B input);
int main() {
A a = A();
Foo(a);
return 0;
}
void Foo (A input) {
B b = B();
Foo(b);
}
void Foo (B input) {
printf("%s", input.toString());
}

注意到这个程序有什么有趣的东西了吗?我们可以在定义之前main()调用Foo(A)。这就是将Foo(A)声明置于main()定义之上的好处。编译器知道Foo(A)存在,因此即使我们还没有它的定义,我们也可以从main()调用它。

使用这样的声明真正有趣的事情是,一旦我们有了声明我们就可以按任何顺序放置定义。因此,我们可以这样做,例如:

void Foo (A input);
void Foo (B input);
int main() {
A a = A();
Foo(a);
return 0;
}
void Foo (B input) {
printf("%s", input.toString());
}
void Foo (A input) {
B b = B();
Foo(b);
}

或者这个:

void Foo (A input);
void Foo (B input);
void Foo (A input) {
B b = B();
Foo(b);
}
int main() {
A a = A();
Foo(a);
return 0;
}

void Foo (B input) {
printf("%s", input.toString());
}

甚至这个:

void Foo (B input) {
printf("%s", input.toString());
}
int main() {
A a = A();
Foo(a);
return 0;
}
void Foo (A input) {
B b = B();
Foo(b);
}

因为这一切都是在声明之后,所以在这种情况下定义顺序无关紧要


在我离开之前值得一提的是:如果我们有这样的声明块:

void Foo (A input);
void Foo (B input);

重新排列此块的顺序无关紧要。因此,我们也可以这样做:

void Foo (B input);
void Foo (A input);

只要这些声明出现在所有定义之前,我们仍然很好。