为什么这两个构造函数一起不会产生歧义错误?

Why do these two constructors together not produce an ambiguity error?

本文关键字:歧义 错误 一起 构造函数 两个 为什么      更新时间:2023-10-16

考虑以下内容:

class A
{
private:
    A() {}
public:
    A(int x = 0) {}
};

int main()
{
    A a(1);
    return 0;
}

我有两个构造函数,一个是默认的,另一个是用默认参数转换构造函数。当我尝试编译代码时,我预计会出现歧义错误,但是编译器没有生成。

即使我没有创建A的实例,它也不会产生歧义错误。

int main()
{
    return 0;
}

为什么?

没有编译错误,因为您的代码中不存在错误。就像定义两个函数一样:它们需要有不同的签名,除此之外,这是可能的。歧义编译错误仅在尝试调用其中一个函数时出现。调用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有不同的特征。在这种情况下,编译器可以解决歧义,如果在const对象上调用f,则会调用const方法,反之亦然。

您的代码可以编译,因为没有歧义。您创建了一个具有两个构造函数的类,一个总是接受0个参数,另一个总是接受一个参数,一个int。然后,明确地以int值调用构造函数。int有默认值并不重要,它仍然是一个完全不同的签名。构造函数有潜在的二义性并不重要,编译器只会在特定的调用确实有二义性时才会报错。

当你创建一个没有参数的A实例时,它不知道你想调用哪个构造函数:是默认构造函数,还是接受形参值为0的int型构造函数。在这种情况下,如果c++注意到私有构造函数不合格就好了,但这并不总是可能的。

这种行为在某些情况下是有用的(例如,如果你有一些涉及模板的重载,如果给定正确的类型,其中一些将重叠),尽管对于像这样的简单情况,我只会使用默认参数创建单个构造函数(最好标记为显式,除非你有一个非常非常好的理由让它隐式,然后我会再次猜测这个原因只是为了确保!)

—EDIT—

让我们玩一玩,并尝试进一步探索正在发生的事情。

// 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 !

在c++中声明可能有歧义的函数不会产生任何歧义错误。当您试图以不明确的方式将引用到这些函数时,就会产生歧义。在你的例子中,如果你试图默认构造你的对象,就会产生歧义。

严格地说,c++程序中存在潜在的二义性声明(即可以以一种不明确的方式引用)是完全正常的。例如,乍一看,这些重载函数看起来很好

void foo(double);
void foo(int);

但是调用foo(1u)会触发歧义错误。所以,再一次,歧义性是你引用前面声明的函数的方式的属性,而不是函数声明本身的属性。

下面是我在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的观点,我认为在他的例子中会出现歧义错误。恕我直言,这样会更加一致。

不会产生歧义,因为在编写A a(1)时,在向其传递参数时甚至没有考虑私有构造函数,并且私有构造函数不接受任何参数。

但是,如果您编写A a,则会出现歧义,因为两个构造函数都是候选的,编译器无法决定调用哪个。