Тарифы Услуги Сим-карты

C программирование конструктор объектов типы. Конструкторы и деструкторы. Конструкторы, параметризированные аргументами переменной длины

Функции-члены класса

#include

// содержит функции ввода-вывода

#include

// содержит функцию CharToOem

#include

// содержит математические функции

class Dot

// класс точки

// закрытые члены класса

char name ;

// имя точки

double x , y ;

// координаты точки

// открытые члены класса public :

// конструктор с параметрами

Dot (char Name , double X , double Y) { name = Name ; x = X ; y = Y ; }

void Print () ;

Здесь значение, переданное в конструктор при объявлении объекта A , используется для инициализации закрытых членов name , x и y этого объекта.

Фактически синтаксис передачи аргумента конструктору с параметрами является сокращенной формой записи следующего выражения:

Dot A = Dot ("A" , 3 , 4) ;

Однако практически всегда используется сокращенная форма синтаксиса, приведенная в примере.

В отличие от конструктора, деструктор не может иметь параметров. Понятно, почему это сделано: незачем передавать аргументы удаляемому объекту.

Правила для конструкторов

Приведем правила, которые существуют для конструкторов:

конструктор класса вызывается всякий раз, когда создается объект его класса;

конструктор обычно инициализирует данные-члены класса и резервирует память для динамических членов;

конструктор имеет то же имя, что и класс, членом которого он является; для конструктора не указывается тип возвращаемого значения; конструктор не может возвращать значение; конструктор не наследуется;

класс может иметь несколько перегруженных конструкторов;

конструктор не может быть объявлен с модификатором const , volatile , static или virtual ;

если в классе не определен конструктор, компилятор генерирует конструктор по умолчанию , не имеющий параметров.

Правила для деструкторов

Для деструкторов существуют следующие правила: деструктор вызывается при удалении объекта;

деструктор обычно выполняет работу по освобождению памяти, занятой объектом;

деструктор имеет то же имя, что и класс, которому он принадлежит, с предшествующим символом ~ ; деструктор не может иметь параметров; деструктор не может возвращать значение; деструктор не наследуется; класс не может иметь более одного деструктора;

деструктор не может быть объявлен с модификатором const , volatile или static ;

если в классе не определен деструктор, компилятор генерирует деструктор по умолчанию .

Подчеркнем еще раз: конструкторы и деструкторы в C++ вызываются автоматически, что гаран-

тирует правильное создание и удаление объектов класса.

Список инициализации элементов

Обычно данные-члены класса инициализируются в теле конструктора, однако существует и другой способ инициализации – с помощью списка инициализации элементов. Список инициализации элементов отделяется двоеточием от заголовка определения функции и содержит данные-члены и базовые классы, разделенные запятыми. Для каждого элемента в круглых скобках непосредственно за ним указывается один или несколько параметров, используемых при инициализации. Синтаксис списка инициализации имеет вид:

ConstrName (parl , par2) : mem1 (parl) , mem2 (par2) , … { …}

В следующем примере для инициализации класса используется список инициализации элементов.

class Dot

char name ;

// имя точки

double x , y ;

// координаты точки

public:

// конструктор со списком инициализации

Dot (char Name) : name (Name) , x (0) , y (0) { }

Хотя выполнение инициализации в теле конструктора или с помощью списка инициализации – дело вкуса программиста, список инициализации является единственным методом инициализации данныхконстант и ссылок. Если членом класса является объект, конструктор которого требует задания значений одного или нескольких параметров, то единственно возможным способом его инициализации также является список инициализации.

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

C++ определяет два специальных вида конструкторов: конструктор по умолчанию, о котором мы упоминали выше, и конструктор копирования. Конструктор по умолчанию не имеет параметров (или все его параметры должны иметь значения по умолчанию) и вызывается при создании объекта, которому не заданы аргументы. Следует избегать двусмысленности при вызове конструкторов. В приведенном ниже примере два конструктора по умолчанию являются двусмысленными:

class Т

public:

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

Т (int i = 0) ;

// конструктор с одним необязательным параметром

// может быть использован как конструктор по умолчанию

void main ()

// Использует конструктор T::T (int)

// Не верно; неоднозначность вызова Т::Т () или Т::Т (int = 0)

В данном случае, чтобы устранить неоднозначность, достаточно удалить из объявления класса конструктор по умолчанию.

Конструкторы копирования

Конструктор копирования создает объект класса, копируя при этом данные из уже существующего объекта данного класса. В связи с этим он имеет в качестве единственного параметра константную ссылку на объект класса (const Т& ) или просто ссылку на объект класса (Т& ). Использование первого предпочтительнее, так как последний не позволяет копировать константные объекты.

рования по умолчанию. В C++ различают поверхностное и глубинное копирование данных.

При поверхностном копировании происходит передача только адреса от одной переменной к другой, в результате чего оба объекта указывают на одни и те же ячейки памяти. Конструктор копирования по умолчанию, созданный компилятором, создает буквальную (или побитную) копию объекта, то есть осуществляет поверхностное копирование. Полученная копия объекта, скорее всего, будет непригодной, если она содержит указатели или ссылки.

В случае глубинного копирования происходит действительное копирование значений всех переменных из одной области памяти в другую. Если эти указатели или ссылки ссылаются на динамически распределенные объекты или на объекты, встроенные в копируемый объект, они будут недоступны в созданной копии объекта. Поэтому для классов, содержащих указатели и ссылки, следует включать в определение класса конструктор копирования, который будет осуществлять глубинное копирование, не полагаясь на созданный компилятором конструктор копирования по умолчанию. В этом конструкторе, как правило, выполняется копирование динамических структур данных, на которые указывают или на которые ссылаются члены класса. Класс должен содержать конструктор копирования, если он перегружает оператор присваивания.

Если в классе не определен конструктор, компилятор пытается сгенерировать собственный конструктор по умолчанию и, если нужно, собственный конструктор копирования. Эти сгенерированные компилятором конструкторы рассматриваются как открытые функции-члены. Подчеркнем, что компилятор генерирует эти конструкторы, если в классе не определен никакой другой конструктор.

Приведём пример использования конструктора копирования:

class Dot

// класс точки

// закрытые члены класса

char name ;

// имя точки

double x , y ;

// координаты точки

public:

// открытые члены класса

// конструкторы с параметрами

Dot (char Name , double X , double Y)

{ name = Name ; x = X ; y = Y ; }

Dot (char Name) : name(Name) , x (0) , y (0)

Dot (char Name , const Dot& A)

{ name = Name ; x = A.x ; y = A.y ; }

// конструктор копирования

Dot (const Dot& A)

{ name = (char ) 226 ; x = A.x ; y = A.y ; }

void Print () ;

// выводит на экран имя и координаты текущей точки

void main ()

Dot A ("A", 3 , 4) ;

// вызов конструктора Dot (char Name , double X , double Y)

// вызов конструктора Dot (char Name)

// выводит на экран:

Координаты точки т :

// вызов конструктора копирования Dot (const Dot& A)

// выводит на экран:

Координаты точки т :

Dot E ("E", A) ;

// вызов конструктора Dot (char Name , const Dot& A)

// выводит на экран:

Координаты точки E:

Конструктор копирования по умолчанию выполняет поверхностное копирование, которое в нашей задаче копирует не только координаты, но и имена точки. Хотя наш объект не содержит динамических чле-

Начнем с того, что когда мы создаем элементы (переменные) класса, мы не можем присвоить им значения в самом определении класса. Компилятор выдаст ошибку. Поэтому нам необходимо создавать отдельный метод (так называемую set-функцию) класса, с помощью которого и будет происходить инициализация элементов. При этом, если необходимо создать, к примеру, 20 объектов класса, то чтобы инициализировать элементы потребуется 20 раз вызвать set-функции.

Тут нам как раз сможет помочь конструктор класса. Кстати, конструктор (от слова construct — создавать) – это специальный метод класса, который предназначен для инициализации элементов класса некоторыми начальными значениями.

В отличии от конструктора, деструктор (от слова destruct — разрушать) — специальный метод класса, который служит для уничтожения элементов класса. Чаще всего его используют тогда, когда в конструкторе,при создании объекта класса, динамически был выделен участок памяти и необходимо эту память очистить, если эти значения уже не нужны для дальнейшей работы программы.

Важно запомнить:

  1. конструктор и деструктор, мы всегда объявляем в разделе public ;
  2. при объявлении конструктора, тип данных возвращаемого значения не указывается, в том числе — void !!!;
  3. у деструктора также нет типа данных для возвращаемого значения, к тому же деструктору нельзя передавать никаких параметров;
  4. имя класса и конструктора должно быть идентично;
  5. имя деструктора идентично имени конструктора, но с приставкой ~ ;
  6. В классе допустимо создавать несколько конструкторов, если это необходимо. Имена, согласно пункту 2 нашего списка, будут одинаковыми. Компилятор будет их различать по передаваемым параметрам (как при перегрузке функций). Если мы не передаем в конструктор параметры, он считается конструктором по умолчанию;
  7. Обратите внимание на то, что в классе может быть объявлен только один деструктор;

Сразу хочу привести пример, который доступно покажет, как работает конструктор:

# include using namespace std; class AB //класс { private: int a; int b; public: AB() //это конструктор: 1) у конструктора нет типа возвращаемого значения! в том числе void!!! // 2) имя должно быть таким как и у класса (в нашем случае AB) { a = 0;//присвоим начальные значения переменным b = 0; cout << "Работа конструктора при создании нового объекта: " << endl;//и здесь же их отобразим на экран cout << "a = " << a << endl; cout << "b = " << b << endl << endl; } void setAB() // с помощью этого метода изменим начальные значения заданные конструктором { cout << "Введите целое число а: "; cin >> a; cout << "Введите целое число b: "; cin >> b; } void getAB() //выведем на экран измененные значения { cout << "a = " << a << endl; cout << "b = " << b << endl << endl; } }; int main() { setlocale(LC_ALL, "rus"); AB obj1; //конструктор сработает на данном этапе (во время создания объекта класса) obj1.setAB(); //присвоим новые значения переменным obj1.getAB(); //и выведем их на экран AB obj2; //конструктор сработает на данном этапе (во время создания 2-го объекта класса) return 0; }

Результат работы программы:

Работа конструктора при создании нового объекта: a = 0 b = 0 Введите целое число а: 34 Введите целое число b: 67 a = 34 b = 67 Работа конструктора при создании нового объекта: a = 0 b = 0

Как видно из результата работы программы, конструктор срабатывает сразу, при создании объектов класса, поэтому, явно вызывать конструктор не нужно, он сам «приходит»:)

Хочется еще добавить, что, как и обычным функциям, мы можем передавать конструктору параметры. Через параметры, конструктору можно передавать любые данные, которые будут необходимы при инициализации объектов класса.

Рассмотрим еще один пример, это все та же программа, только в код внесены некоторые изменения. Тут же покажем принцип работы деструктора:

# include using namespace std; class AB //класс { private: int a; int b; public: AB(int A, int B) //эти параметры мы передадим при создании объекта в main { a = A;//присвоим нашим элементам класса значения параметров b = B; cout << "Тут сработал конструктор, который принимает параметры: " << endl;//и здесь же их отобразим на экран cout << "a = " << a << endl; cout << "b = " << b << endl << endl; } void setAB() { cout << "Введите целое число а: "; cin >> a; cout << "Введите целое число b: "; cin >> b; } void getAB() { cout << "a = " << a << endl; cout << "b = " << b << endl << endl; } ~AB() // это деструктор. не будем заставлять его чистить память, пусть просто покажет где он сработал { cout << "Тут сработал деструктор" << endl; } }; int main() { setlocale(LC_ALL, "rus"); AB obj1(100, 100); //передаем конструктору параметры obj1.setAB(); //присвоим новые значения переменным obj1.getAB(); //и выведем их на экран AB obj2(200, 200); //передаем конструктору параметры }

Смотрим результат работы программы :

Тут сработал конструктор, который принимает параметры: a = 100 b = 100 Введите целое число а: 333 Введите целое число b: 333 a = 333 b = 333 Тут сработал конструктор, который принимает параметры: a = 200 b = 200 Тут сработал деструктор Тут сработал деструктор

Деструктор срабатывает в тот момент, когда завершается работа программы и уничтожаются все данные. Мы его не вызывали – он сработал сам. Как видно, он сработал 2 раза, так как и конструктор. Уже от себя добавлю, что, в первую очередь, он удалил второй созданный объект (где a = 200, b = 200), а затем первый (где a = 100, b = 100). «Последним пришёл - первым вышел».

Если нет явным образом опредёленных конструкторов в классе , то компилятор использует конструктор по умолчанию, опредёленный неявным способом, который аналогичен «чистому» [уточнить ] конструктору по умолчанию. Поэтому, класс не гарантирует наличия конструктора по умолчанию (то есть когда программист явным образом определяет только конструктор, который не по умолчанию). Некоторые программисты явным образом задают конструктор по умолчанию по привычке, чтобы не забыть в дальнейшем, но это не обязательно. В C++ только массивы имеют конструкторы по умолчанию, которые создают каждый элемент при помощи конструктора по умолчанию для их типа.

В C++ и Java если производный класс не вызывает явным образом конструктор базового класса (в C++ в списке инициализации, в Java используя super() в первой строчке), то конструктор по умолчанию вызывается неявно. Если базовый класс не имеет конструктора по умолчанию, то это считается ошибкой. В C++ если поле экземпляра класса явным образом не инициализировано в списке, то вызывается конструктор по умолчанию для инициализации этого поля. Если такой тип не имеет конструктора по умолчанию, то это также считается ошибкой.


Wikimedia Foundation . 2010 .

Смотреть что такое "Конструктор по умолчанию" в других словарях:

    Конструктор - получить на Академике рабочий купон на скидку Ашан или выгодно конструктор купить с бесплатной доставкой на распродаже в Ашан

    конструктор по умолчанию - Конструктор, создаваемый компилятором при отсутствии конструктора класса. [ГОСТ Р 54456 2011] Тематики телевидение, радиовещание, видео EN default constructor … Справочник технического переводчика

    У этого термина существуют и другие значения, см. Конструктор. В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor) специальный блок инструкций, вызываемый при создании объекта.… … Википедия

    В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor) специальный блок инструкций, вызываемый при создании объекта, причём или при его объявлении (располагаясь в стеке или в статической… … Википедия

    Конструктором копирования (в англоязычной литературе используется термин copy constructor) называется специальный конструктор в языке программирования C++, применяемый для создания нового объекта как копии уже существующего. Такой конструктор… … Википедия

    В компьютерном программировании нуль арным конструктором (в англ. языке используется термин nullary constructor) называют конструктор, не принимающий аргументы. Содержание 1 Объектно ориентированные конструкторы 1.1 … Википедия

    C++0x будущая версия стандарта языка C++, вместо ныне существующего ISO/IEC 14882:2003. Новый стандарт будет включать дополнения в ядре языка и расширение STL, включая большую часть TR1 кроме, вероятно, библиотеки специальных… … Википедия

    Стандартная библиотека языка программирования C++ fstream iomanip ios iostream sstream Стандартная библиотека шаблонов … Википедия

    Стандартная библиотека шаблонов (STL) (англ. Standard Template Library) набор согласованных обобщенных алгоритмов, контейнеров, средств доступа к их содержимому и различных вспомогательных функций. Стандартная библиотека шаблонов до включения в… … Википедия

    C++11 или ISO/IEC 14882:2011 (в процессе работы над стандартом носил условное наименование C++0x) новая версия стандарта языка C++, вместо ранее действовавшего ISO/IEC 14882:2003. Новый стандарт включает дополнения в ядре… … Википедия

Конструктор — функция, предназначенная для инициализации объектов класса.Рассмотрим класс date :

class date
{
int day, month, year;
public :
set(int , int , int );
};

Нигде не утверждается, что объект должен быть инициализирован, и программист может забыть инициализировать его или сделать это дважды.
ООП дает возможность программисту описать функцию, явно предназначенную для инициализации объектов. Поскольку такая функция конструирует значения данного типа, она называется конструктором . Конструктор всегда имеет то же имя, что и сам класс и никогда не имеет возвращаемого значения. Когда класс имеет конструктор, все объекты этого класса будут проинициализированы.

class date {
int day, month, year;
public :
date(int , int , int ); // конструктор
};

Если конструктор требует аргументы, их следует указать:

date today = date(6,4,2014); // полная форма
date xmas(25,12,0); // сокращенная форма
// date my_burthday; // недопустимо, опущена инициализация

Если необходимо обеспечить несколько способов инициализации объектов класса, задается несколько конструкторов:

class date {
int month, day, year;
public :
date(int , int , int ); // день месяц год
date(char *); // дата в строковом представлении
date(); // дата по умолчанию: сегодня
};

Конструкторы подчиняются тем же правилам относительно типов параметров, что и перегруженные функции . Если конструкторы существенно различаются по типам своих параметров, то компилятор при каждом использовании может выбрать правильный:

date july4("Февраль 27, 2014" );
date guy(27, 2, 2014);
date now; // инициализируется по умолчанию

Одним из способов сократить количество перегруженных функций (в том числе и конструкторов) является использование значений по умолчанию.

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

Конструктор, не требующий параметров, называется конструктором по умолчанию . Это может быть конструктор с пустым списком параметров или конструктор, в котором все аргументы имеют значения по умолчанию.
Конструкторы могут быть перегруженными, но конструктор по умолчанию может быть только один.

class date
{
int month, day, year;
public :
date(int , int , int );
date(char *);
date(); // конструктор по умолчанию
};

При создании объекта вызывается конструктор, за исключением случая, когда объект создается как копия другого объекта этого же класса, например:

date date2 = date1;

Однако имеются случаи, в которых создание объекта без вызова конструктора осуществляется неявно:

  • формальный параметр – объект, передаваемый по значению, создается в стеке в момент вызова функции и инициализируется копией фактического параметра;
  • результат функции – объект, передаваемый по значению, в момент выполнения оператора return копируется во временный объект, сохраняющий результат функции.

Во всех этих случаях транслятор не вызывает конструктора для вновь создаваемого объекта:

  • date2 в приведенном определении;
  • для создаваемого в стеке формального параметра;
  • для временного объекта, сохраняющего значение, возвращаемое функцией.

Вместо этого в них копируется содержимое объекта-источника:

  • date1 в приведенном примере;
  • фактического параметра;
  • объекта-результата в операторе return .

Конструктор копии

Как правило, при создании нового объекта на базе уже существующего происходит поверхностное копирование, то есть копируются те данные, которые содержит объект-источник. При этом если в объекте-источнике имеются указатели на динамические переменные и массивы, или ссылки , то создание копии объекта требует обязательного дублирования этих объектов во вновь создаваемом объекте. С этой целью вводится конструктор копии, который автоматически вызывается во всех перечисленных случаях. Он имеет единственный параметр — ссылку на объект-источник:

class String
{
char *str;
int size;
public :
String(String&); // Конструктор копирования
};
String::String(String& right) { // Создает копии динамических
// переменных и ресурсов
str = new char ;
strcpy(str, right->str);
size = right->size;
}

Деструкторы

Определяемый пользователем класс имеет конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие. Деструктор обеспечивает соответствующую очистку объектов указанного типа. Имя деструктора представляет собой имя класса с предшествующим ему знаком «тильда» ~ . Так, для класса X деструктор будет иметь имя ~X() . Многие классы используют динамическую память, которая выделяется конструктором, а освобождается деструктором.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class date
{
int day, year;
char *month;
public :
date(int d, char * m, int y)
{
day = d;
month = new char ;
strcpy_s(month, strlen(m)+1,m);
year = y;
}
~date() { delete month; } // деструктор
};


Пусть имеется класс vect , реализующий защищенный массив, и необходимо хранить несколько значений для каждого такого массива: возраст, вес и рост группы лиц. Группируем 3 массива внутри нового класса.

Конструктор нового класса имеет пустое тело и список вызываемых конструкторов класса vect , перечисленных после двоеточия (:) через запятую (,). Они выполняются с целым аргументом i , создавая 3 объекта класса vect: a, b, c .

Конструкторы членов класса всегда выполняются до конструктора класса, в котором эти члены описаны. Порядок выполнения конструкторов для членов класса определяется порядком объявления членов класса. Если конструктору члена класса требуются аргументы, этот член с нужными аргументами указывается в списке инициализации. Деструкторы вызываются в обратном порядке.
{
system("chcp 1251" );
system("cls" );
multi_v f(3);
for (int i = 0; i <= f.getSize(); i++)
{
f.a.element(i) = 10 + i;
f.b.element(i) = 20 + 5 * i;
f.c.element(i) = 120 + 5 * i;
}
for (int i = 0; i <= f.getSize(); i++)
{
cout << f.a.element(i) << "лет \t" ;
cout << f.b.element(i) << "кг \t" ;
cout << f.c.element(i) << "см" << endl;
}
cin.get();
return 0;
}


При выполнении программы перед выходом из блока main для каждого члена vect будет вызываться индивидуальный деструктор. Результат работы программы

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; };

Есть ли различия? Что лучше использовать?

2 ответа

Vlad from Moscow

Эти два объявления классов не эквивалентны.

Во втором объявлении класса конструктор объявлен со спецификатором функции 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; }

то данный вызов

будет корректным, так как имеется функция с одним параметром.

IxSci

Различия уже объяснил @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 у одноаргументнова конструктора - защита от случайных ошибок.

Ну и самое главное, мы имеем лишь один конструктор, который инициализирует поля. Все остальные действуют через него, что позволяет минимизировать ошибки инициализации.