#cpp #классы #перегрузка_операторов #конструктор #language_lawyer
class A
{
public:
A():a(0),b(0) {}
explicit A(int x): a(x), b(0) {}
A(int x, int y): a(x), b(y) {}
private:
int a,b;
};
и
class A
{
public:
explicit A(int x=0, int y=0): a(x), b(y) {}
private:
int a,b;
};
Есть ли различия? Что лучше использовать?
Ответы
Ответ 1
Эти два объявления классов не эквивалентны.
Во втором объявлении класса конструктор объявлен со спецификатором функции explicit,
а это ограничивает применение этого конструктора в различных ситуациях.
В первом же объявлении класса только конструктор преобразования объявлен со спецификатором
функции explicit. А это означает, что другие конструкторы вы можете вызывать неявно.
То есть первое объявление предоставляет больше возможностей по использованию класса.
Рассмотрите следующую демонстрационную программу
#include
struct A
{
explicit A( int x = 0, int y = 0 ) : x( x ), y( y ) {}
int x;
int y;
};
struct B
{
B() : x( 0 ), y( 0 ) {}
explicit B( int x ): x( x ), y( 0 ) {}
B( int x, int y ): x( x ), y( y ) {}
int x;
int y;
};
void f( const A &a )
{
std::cout << "a.x = " << a.x << ", a.y = " << a.y << std::endl;
}
void g( const B &b )
{
std::cout << "b.x = " << b.x << ", b.y = " << b.y << std::endl;
}
int main()
{
// f( {} );
// f( { 1, 2 } );
g( {} );
g( { 1, 2 } );
}
Ее вывод на консоль:
b.x = 0, b.y = 0
b.x = 1, b.y = 2
В этой программе два вызова функции f закомментированы, так как если их раскомментировать,
то компилятор выдаст сообщение об ошибке.
Другое важное отличии состоит в том, что один класс имеет всего лишь один конструктор
с заданной сигнатурой, а другой класс имеет три конструктора с различными сигнатурами.
Рассмотрите еще один демонстрационный пример
struct A
{
explicit A( int x = 0, int y = 0 ) : x( x ), y( y ) {}
int x;
int y;
};
struct B
{
B() : x( 0 ), y( 0 ) {}
explicit B( int x ): x( x ), y( 0 ) {}
B( int x, int y ): x( x ), y( y ) {}
int x;
int y;
};
struct C
{
//friend A::A();
friend B::B();
};
int main()
{
}
Здесь в классе C вы можете объявить конструктор по умолчанию класса B в качестве
друга класса С. Однако вы не можете сделать то же самое с конструктором по умолчанию
класса A, чтобы объявить его другом класса C, так как конструктор по умолчанию в классе
A имеет другую сигнатуру.
Вам уже придется писать
struct C
{
friend A::A( int, int );
};
а это может быть не тем, что вы хотели бы получить. То есть если вы, например, хотели,
чтобы другом был конструктор, который вызывается исключительно без аргументов.
То есть, опять-таки, когда имеются отдельные конструкторы, то ваши возможности более
широкие.
Если рассматривать не конструкторы, а функции, то разница имеется еще более существенная.
Аргументы по умолчанию не влияют на тип функции. Поэтому, например, если вы объявили
функцию как
void f( int, int = 0 );
то, несмотря на аргумент по умолчанию и того факта, что вы можете ее вызывать как
f( value );
тем не менее ее тип void( int, int ). А это в свою очередь означает, что вы не можете,
например, написать
void h( void f( int ) )
{
f( 10 );
}
void f( int x, int y = 0 ) { std::cout << "x = " << x << ", y = " << y << std::endl; }
//
h( f );
так как параметр функции h имеет тип void( int )., а у функции, используемой в качестве
аргумента, тип void( int, int )
Если же вы объявите две функции вместо одной
void h( void f( int ) )
{
f( 10 );
}
void f( int x ) { std::cout << "x = " << x << std::endl; }
void f( int x, int y ) { std::cout << "x = " << x << ", y = " << y << std::endl; }
то данный вызов
h( f );
будет корректным, так как имеется функция с одним параметром.
Ответ 2
Различия уже объяснил @Vlad from Moscow, я лишь предложу, из двух вариантов в вопросе,
третий вариант:
class A
{
public:
A():A(0, 0) {}
explicit A(int x): A(x, 0) {}
A(int x, int y): a{x}, b{y} {}
private:
int a,b;
};
На мой взгляд именно этот вариант является лучшим, т.к. он имеет явный конструктор
с одним аргументом, что является хорошей практикой и уберегает от некоторых ошибок.
С другой стороны, explicit для конструкторов, у которых больше или меньше одного аргумента,
на мой взгляд, является лишним. Т.к. случайно создать объект из более чем одного аргумента
проблематично, а именно за этим мы и приписали explicit у одноаргументнова конструктора
— защита от случайных ошибок.
Ну и самое главное, мы имеем лишь один конструктор, который инициализирует поля.
Все остальные действуют через него, что позволяет минимизировать ошибки инициализации.