Страницы

Поиск по вопросам

пятница, 10 января 2020 г.

Конструктор по умолчанию

#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 у одноаргументнова конструктора — защита от случайных ошибок. Ну и самое главное, мы имеем лишь один конструктор, который инициализирует поля. Все остальные действуют через него, что позволяет минимизировать ошибки инициализации.

Комментариев нет:

Отправить комментарий