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

C++ 为什么这两个构造函数一起不会产生歧义错误?,c++,constructor,ambiguity,C++,Constructor,Ambiguity,考虑以下几点: class A { private: A() {} public: A(int x = 0) {} }; int main() { A a(1); return 0; } 我有两个构造函数,一个是默认构造函数,另一个是使用默认参数转换构造函数。当我试图编译代码时,我预期会出现歧义错误,但编译器不会产生歧义错误 即使我没有创建A的实例,它也不会产生歧义错误 int main() { return 0; } 为什么会这样?没有编译错误,

考虑以下几点:

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
方法,反之亦然。

下面是我在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;
}
更新

< >我理解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_的观点,我认为在他的例子中会出现歧义错误。我希望它更加一致。

之所以不会出现歧义,是因为在编写
A(1)
时甚至没有考虑私有构造函数,因为您向它传递了一个参数,而私有构造函数不接受任何参数


但是,如果你写的代码> a < /COD>,那么就会有歧义,因为两个构造函数都是候选,编译器不能决定调用哪个。

声明C++中可能含糊不清的函数不会产生任何模糊错误。当您试图以模棱两可的方式引用这些函数时,就会出现模棱两可的情况。在您的示例中,如果尝试默认构造对象,则会出现歧义

严格地说,C++程序中的声明是完全正常的(可能是模棱两可的)。例如,乍一看,这些重载函数看起来不错

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

但是调用
foo(1u)
将触发歧义错误。因此,再次强调,模糊性是指引用先前声明的函数的方式的属性,而不是函数声明本身的属性。

您的代码编译是因为没有模糊性。您创建了一个包含两个构造函数的类,一个总是接受0个参数,另一个总是接受一个参数,一个int。然后您明确地调用了接受int值的构造函数。这个int有一个默认值并不重要,它仍然是一个完全不同的签名。构造函数可能是不明确的并不重要,编译器只会在特定调用实际上是不明确的时候发出抱怨

当您创建一个不带参数的实例时,它不知道要调用哪个构造函数:默认构造函数,或者使用参数值为0的int的构造函数。在这种情况下,如果C++注意到私有构造函数是不合格的,但这并不总是可能的,那么这将是很好的。p> 这种行为在某些情况下非常有用(例如,如果有一些重载涉及模板,如果给定了正确的类型,其中一些重载将重叠),但是对于这样的简单情况,我只使用默认参数创建单个构造函数(最好标记为显式,除非你有一个非常好的理由让它隐式,然后我会再次猜测这个原因只是为了确定!)

--编辑--

让我们从中获得一些乐趣,并尝试进一步探索正在发生的事情

// 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)的定义没有任何更改

我没有这本书的副本
// 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?)
}