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

Ron*_*n_s 7 c++ constructor ambiguity

考虑以下:

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


int main()
{
    A a(1);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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

int main()
{
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是为什么?

Luc*_*ore 6

没有编译错误,因为代码中不存在错误.就像定义两个函数一样:它们需要有不同的签名,除此之外,它是可能的.仅当您尝试调用其中一个函数时,才会出现歧义编译错误.尝试调用默认构造函数也会发生同样的情况A,即使它是私有的:

class A
{
private:
    A() {}
public:
    A(int x = 0) {}
    void f() {}
    void f(int x = 0) {}
};
Run Code Online (Sandbox Code Playgroud)

这个编译,尝试调用f()没有参数失败,这是有道理的.

还试试:

class A
{
private:
    A() {}
public:
    A(int x = 0) {}
    void f() {}
    void f() const {}
};
Run Code Online (Sandbox Code Playgroud)

这应该给出错误吗?不,因为两人f有不同的签名.在这种情况下,编译器可以解决多义,如果你调用f一个上const对象,该const方法被调用,反之亦然.


Mar*_*arc 2

您的代码可以编译,因为没有歧义。您创建了一个具有两个构造函数的类,一个构造函数始终采用 0 个参数,另一个构造函数始终采用 1 个参数(一个 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?)
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们正在利用编译器不了解标头的事实;当它查看 .cpp 文件时,预处理器已经用标头正文替换了 #includes。我正在扮演自己的预处理器,做一些危险的事情,比如提供一个类的多个不同的定义。稍后,链接器的工作之一就是丢弃除其中一个定义之外的所有定义。如果它们没有以完全正确的方式对齐,就会发生各种不好的事情,因为你将处于未定义行为的暮光区。

请注意,我很小心地在每个编译单元中为我的类提供完全相同的布局;每个定义都恰好有 1 个 int 和 0 个虚方法。请注意,我没有引入任何额外的方法(尽管这可能有效;仍然应该以极大的怀疑的态度看待这样的事情),唯一改变的是一些非虚拟成员函数(实际上是构造函数),然后仅删除默认构造函数。更改和删除默认值不会改变 A::A(int) 的定义。

我身上没有规范的副本,所以我不能说我的仔细更改是否属于未定义行为或实现特定行为,但我会将其视为生产代码,并避免利用此类技巧。

巴兹内部使用的论点的最终答案是……42!