13.13 – Копирующая инициализация

Добавлено 20 июля 2021 в 06:47
Глава 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 &copy) :
        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 возвращается по значению.

Теги

C++ / CppLearnCppДля начинающихКонструктор / Constructor / ctor (программирование)Конструктор копированияОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.