13.13 – Копирующая инициализация
Рассмотрим следующую строку кода:
int x = 5;
Эта инструкция использует копирующую инициализацию для инициализации новой созданной целочисленной переменной x
значением 5.
Однако классы немного сложнее, поскольку для инициализации они используют конструкторы. В этом уроке будут рассмотрены темы, связанные с копирующей инициализацией для классов.
Копирующая инициализация для классов
Учитывая наш класс Fraction
:
#include <cassert>
#include <iostream>
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
// Конструктор по умолчанию
Fraction(int numerator=0, int denominator=1) :
m_numerator(numerator), m_denominator(denominator)
{
assert(denominator != 0);
}
friend std::ostream& operator<<(std::ostream& out, const Fraction &f1);
};
std::ostream& operator<<(std::ostream& out, const Fraction &f1)
{
out << f1.m_numerator << "/" << f1.m_denominator;
return out;
}
Рассмотрим следующий код:
int main()
{
Fraction six = Fraction(6);
std::cout << six;
return 0;
}
Если бы вы скомпилировали и запустили этот код, вы бы увидели, что он дает ожидаемый результат:
6/1
Эта форма копирующей инициализации вычисляется так же, как следующая:
Fraction six(Fraction(6));
И, как вы узнали в предыдущем уроке, это потенциально может вызывать как Fraction(int, int)
, так и конструктор копирования Fraction
(который может быть опущен для производительности). Однако, поскольку исключение копирования не гарантируется (до C++17, где исключение копирования в этом конкретном случае теперь является обязательным), для классов лучше избегать копирующей инициализации и вместо этого использовать унифицированную инициализацию.
Правило
Избегайте использования копирующей инициализации и используйте вместо нее унифицированную инициализацию.
Другие места, где используется копирующая инициализация
Есть еще несколько мест, где используется копирующая инициализация, но о двух из них стоит упомянуть отдельно. Когда вы передаете или возвращаете класс по значению, этот процесс использует копирующую инициализацию.
Рассмотрим следующий код:
#include <cassert>
#include <iostream>
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
// Конструктор по умолчанию
Fraction(int numerator=0, int denominator=1) :
m_numerator(numerator), m_denominator(denominator)
{
assert(denominator != 0);
}
// Конструктор копирования
Fraction(const Fraction ©) :
m_numerator(copy.m_numerator), m_denominator(copy.m_denominator)
{
// здесь нет необходимости проверять знаменатель на 0,
// поскольку copy уже должна быть допустимым объектом Fraction
std::cout << "Copy constructor called\n"; // просто чтобы доказать, что это работает
}
friend std::ostream& operator<<(std::ostream& out, const Fraction &f1);
int getNumerator() { return m_numerator; }
void setNumerator(int numerator) { m_numerator = numerator; }
};
std::ostream& operator<<(std::ostream& out, const Fraction &f1)
{
out << f1.m_numerator << "/" << f1.m_denominator;
return out;
}
// в идеале мы должны сделать это с помощью константной ссылки
Fraction makeNegative(Fraction f)
{
f.setNumerator(-f.getNumerator());
return f;
}
int main()
{
Fraction fiveThirds(5, 3);
std::cout << makeNegative(fiveThirds);
return 0;
}
В приведенной выше программе функция makeNegative
принимает Fraction
по значению, а также возвращает Fraction
по значению. Когда мы запускаем программу, мы получаем:
Copy constructor called
Copy constructor called
-5/3
Первый вызов конструктора копирования происходит, когда fiveThirds
передается в качестве аргумента в параметр f
функции makeNegative()
. Второй вызов происходит, когда значение, возвращаемое makeNegative()
, передается обратно в main()
.
В приведенном выше случае нельзя исключить копирование как для аргумента, передаваемого по значению, так и для возвращаемого значения. Однако в других случаях, если аргумент или возвращаемое значение соответствуют определенным критериям, компилятор может отказаться от конструктора копирования. Например:
class Something
{
};
Something foo()
{
Something s;
return s;
}
int main()
{
Something s = foo();
}
В этом случае компилятор, вероятно, исключит конструктор копирования, даже если переменная s
возвращается по значению.