Ваш код компилируется, потому что нет двусмысленности.Вы создали класс с двумя конструкторами, один из которых всегда принимает 0 аргументов, а другой всегда принимает один аргумент, int.Затем вы однозначно вызвали конструктор, принимающий значение типа int.То, что этот int имеет значение по умолчанию, не имеет значения, это все равно совершенно другая подпись.То, что конструкторы потенциально неоднозначны, не имеет значения, компилятор только жалуется, когда конкретный вызов на самом деле неоднозначен.
Когда вы создаете экземпляр A без аргументов, он не знает, какой конструктор вы хотитеcall: конструктор по умолчанию или конструктор, принимающий int со значением параметра 0. В этом случае было бы неплохо, если бы 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, препроцессор уже заменил #include телом заголовка.Я играю за то, чтобы быть моим собственным препроцессором, выполняя некоторые опасные вещи, такие как предоставление нескольких разных определений классаПозже одна из задач компоновщика - исключить все эти определения, кроме одного.Если они не выровняются точно правильным образом, произойдут все виды плохих вещей, так как вы окажетесь в сумеречной зоне неопределенного поведения.
Обратите внимание, что я был осторожен, чтобы предоставить точно такой же макет длямой класс в каждой единице компиляции;каждое определение имеет ровно 1 int и 0 виртуальных методов.Обратите внимание, что я не представил никаких дополнительных методов (хотя это может сработать; все же такие вещи следует рассматривать с большим подозрением), единственное, что изменилось, это некоторые не виртуальные функции-члены (на самом деле, конструкторы), а затем толькоудалить конструктор по умолчанию.Изменение и удаление значения по умолчанию ничего не изменило в определении A :: A (int).
У меня нет копии спецификации, поэтому я не могу сказать, выпадают ли мои осторожные измененияпри неопределенном поведении или поведении, специфичном для реализации, но я бы относился к нему как таковому для производственного кода и не использовал бы такие уловки.
И окончательный ответ на то, какой аргумент используется внутри Baz, - .... 42!