请考虑以下事项:
class A
{
private:
A() {}
public:
A(int x = 0) {}
};
int main()
{
A a(1);
return 0;
}
我有两个构造函数,一个是默认值,另一个是使用默认参数转换构造函数。当我尝试编译代码时,我预计会出现歧义错误,但编译器不会产生错误。
即使我没有创建A
的实例,它也不会产生歧义错误。
int main()
{
return 0;
}
为什么?
答案 0 :(得分:6)
没有编译错误,因为代码中不存在错误。就像定义两个函数一样:它们需要有不同的签名,除此之外,它是可能的。仅当您尝试调用其中一个函数时,才会出现歧义编译错误。尝试调用A
的默认构造函数也会发生同样的情况,即使它是私有的:
class A
{
private:
A() {}
public:
A(int x = 0) {}
void f() {}
void f(int x = 0) {}
};
这个编译,试图在没有参数的情况下调用f()
失败,这是有道理的。
还可以尝试:
class A
{
private:
A() {}
public:
A(int x = 0) {}
void f() {}
void f() const {}
};
这会导致错误吗?不,因为这两个f
有不同的签名。在这种情况下,编译器可以解决歧义,如果您在f
对象上调用const
,const
方法将被调用,反之亦然。
答案 1 :(得分:2)
您的代码编译因为没有歧义。您创建了一个具有两个构造函数的类,一个总是接受0个参数,另一个总是接受一个参数,一个是int。然后,您明确地调用构造函数来获取int值。这个int有一个默认值并不重要,它仍然是一个完全不同的签名。构造函数可能不明确无关紧要,编译器只会在特定调用实际上不明确时抱怨。
当你创建一个没有参数的A实例时,它不知道你要调用哪个构造函数:默认构造函数,或者带参数值为0的int的构造函数。在这种情况下它会很好如果C ++注意到私有构造函数不合格,但这并不总是可行的。
这种行为最终在某些情况下很有用(例如,如果你有一些涉及模板的重载,如果给出正确的类型,其中一些会重叠),虽然对于像这样的简单情况,我只想制作单个构造函数默认参数(最好标记为显式,除非你有一个非常好的理由让它隐含,然后我会猜测这个原因只是为了确定!)
- 编辑 -
让我们玩得开心,并尝试进一步探索正在发生的事情。
// A.h
class A
{
public:
A(); // declare that somewhere out there, there exists a constructor that takes no args. Note that actually figuring out where this constructor is defined is the linker's job
A(int x = 10); // declare that somewhere out there, there exists a constructor that take one arg, an integer. Figuring out where it is defined is still the linker's job. (this line does _not_ declare two constructors.)
int x;
};
// A.cpp
#include "A.h"
A::A() { ... } // OK, provide a definition for A::A()
A::A(int x) { ... } // OK, provide a definition for A::A(int) -- but wait, where is our default param??
// Foo.cpp
#include "A.h"
void Foo()
{
A a1(24); // perfectly fine
A a2; // Ambigious!
}
// Bar.cpp
class A // What's going on? We are redefining A?!?!
{
public:
A();
A(int x); // this definition doesn't have a default param!
int x;
};
void Bar()
{
A a; // This works! The default constructor is called!
}
// Baz.cpp
class A // What's going on? We are redefining A again?!?!
{
public:
//A(); // no default constructor!
A(int x = 42); // this definition has a default param again, but wait, it's different!
int x;
};
void Baz()
{
A a; // This works! A::A(int) is call! (but with what parameter?)
}
请注意,我们正在利用编译器不知道标头的事实;当它查看.cpp文件时,预处理器已经用标头的主体替换了#includes。我正在扮演自己的预处理器,做一些危险的事情,比如提供多个不同的类定义。稍后,链接器的一个工作就是抛弃除了其中一个定义之外的所有定义。如果他们没有以正确的方式对齐,那么各种不好的事情都会发生,因为你将处于未定义行为的暮光区域。
请注意,我小心翼翼地在每个编译单元中为我的类提供完全相同的布局;每个定义都有1个int和0个虚拟方法。请注意,我没有引入任何额外的方法(虽然这可能会起作用;仍然应该非常怀疑地做这样的事情),唯一改变的是一些非虚拟成员函数(真正的构造函数)然后只有删除默认构造函数。更改和删除默认值不会改变A :: A(int)的定义。
我没有关于我的规范的副本,所以我不能说我的细心变化是否属于未定义的行为或特定于实现的行为,但我会将其视为生产代码,并避免利用这些技巧
对Baz内部使用的论证的最终答案是,...... 42!
答案 2 :(得分:1)
这是我在cygwin上用GCC测试的一个稍微修改过的例子:
#include <iostream>
class A
{
private:
A();
public:
A(int x = 0);
};
A::A()
{
std::cout << "Constructor 1.\n" << std::endl;
}
A::A(int x)
{
std::cout << "Constructor 2 with x = " << x << std::endl;
}
int main()
{
A a1(1);
A a2;
return 0;
}
编译器提供以下错误消息:
$ g++ test.cc
test.cc: In function `int main()':
test.cc:28: error: call of overloaded `A()' is ambiguous
test.cc:20: note: candidates are: A::A(int)
test.cc:14: note: A::A()
<强>更新强>
我理解C ++编译器是如何工作的(感谢其他人给出的解释):两个声明是不同的,因此被接受,但是当试图引用默认构造函数时,编译器无法决定它应该使用哪两个。
但是,可以从声明中推断出永远不能调用默认构造函数A()的事实。显然,你有两个具有不同签名的功能
A()
A(int x = 0)
但实际上,A(int x = 0)隐式定义了两个函数:A(int x)和A()。在第二个函数中,x只是一个初始化的局部变量。而不是写
A(int x = 0)
{
...
}
你可以写两个函数:
A(int x)
{
...
}
A()
{
int x = 0;
...
}
与同一个身体相同。这两个函数中的第二个与默认构造函数A()具有相同的签名。这就是为什么当您尝试使用A()构造函数实例化类A时,您将始终遇到编译错误。即使没有明确的声明,例如,也可以检测到这种情况
A a;
所以我完全赞同Ron_s,我希望他的例子中存在歧义错误。恕我直言,这将更加一致。
答案 3 :(得分:1)
在C ++中声明可能不明确的函数不会产生任何歧义错误。当您尝试以模糊的方式将引用到这些函数时,就会出现歧义。在您的示例中,如果您尝试默认构造对象,则会出现歧义。
严格地说,在C ++程序中声明可能含糊不清(即可以模糊方式引用)是完全正常的。例如,乍一看这些重载函数看起来很好
void foo(double);
void foo(int);
但是调用foo(1u)
会触发歧义错误。因此,歧义再次是您引用先前声明的函数的方式的属性,而不是函数声明本身的属性。
答案 4 :(得分:0)
不会出现歧义,因为在编写A a(1)
时甚至没有考虑私有构造函数,因为您将参数传递给它,并且私有构造函数不接受任何参数。
但是,如果你编写A a
,那么就会有歧义,因为两个构造函数都是候选者,编译器无法决定调用哪一个。