12.7 – Инициализация нестатических членов класса

Добавлено 4 июля 2021 в 21:32

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

Но обычным переменным-членам класса (тем, в которых не используется ключевое слово static) значение инициализации по умолчанию можно присвоить напрямую:

#include <iostream>
 
class Rectangle
{
private:
    double m_length{ 1.0 }; // m_length имеет значение по умолчанию 1.0
    double m_width{ 1.0 };  // m_width имеет значение по умолчанию 1.0
 
public:
    void print()
    {
        std::cout << "length: " << m_length << ", width: " << m_width << '\n';
    }
};
 
int main()
{
    Rectangle x{}; // x.m_length = 1.0, x.m_width = 1.0
    x.print();
 
    return 0;
}

Эта программа выдает следующий результат:

length: 1.0, width: 1.0

Инициализация нестатических членов (также называемая внутренними инициализаторами членов класса) предоставляет для переменных-членов значения по умолчанию, которые конструкторы будут использовать, если эти конструкторы сами не предоставляют значения инициализации для этих членов (через список инициализации членов).

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

#include <iostream>
 
class Rectangle
{
private:
    double m_length{ 1.0 };
    double m_width{ 1.0 };
 
public:
 
    // обратите внимание: в этом примере нет конструктора по умолчанию
 
    Rectangle(double length, double width)
        : m_length{ length },
          m_width{ width }
    {
        // m_length и m_width инициализируются конструктором 
        // (значения по умолчанию не используются)
    }
 
    void print()
    {
        std::cout << "length: " << m_length << ", width: " << m_width << '\n';
    }
 
};
 
int main()
{
    Rectangle x{}; // не будет компилироваться, конструктор по умолчанию
                   // не существует, даже если члены имеют значения 
                   // инициализации по умолчанию
 
      return 0;
}

Несмотря на то, что мы предоставили значения по умолчанию для всех членов, конструктор по умолчанию не был предоставлен, поэтому мы не можем создавать объекты Rectangle без аргументов.

Если указано значение инициализации по умолчанию и конструктор инициализирует член через список инициализаторов членов, список инициализаторов членов будет иметь больший приоритет. Следующий пример показывает это:

#include <iostream>
 
class Rectangle
{
private:
    double m_length{ 1.0 };
    double m_width{ 1.0 };
 
public:
 
    Rectangle(double length, double width)
        : m_length{ length },
          m_width{ width }
    {
        // m_length и m_width инициализируются конструктором
        // (значения по умолчанию не используются)
    }
 
    Rectangle(double length)
        : m_length{ length }
    {
        // m_length инициализируется конструктором
        // m_width используется значение по умолчанию (1.0)
    }
 
    void print()
    {
        std::cout << "length: " << m_length << ", width: " << m_width << '\n';
    }
 
};
 
int main()
{
    Rectangle x{ 2.0, 3.0 };
    x.print();
 
    Rectangle y{ 4.0 };
    y.print();
 
    return 0;
}

Эта программа печатает:

length: 2.0, width: 3.0
length: 4.0, width: 1.0

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

Правило


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

Небольшой тест

Вопрос 1

Обновите следующую программу, чтобы использовать инициализацию нестатических членов и списки инициализаторов членов.

#include <string>
#include <iostream>
 
class Ball
{
private:
	std::string m_color;
	double m_radius;
 
public:
	// Конструктор по умолчанию без параметров
	Ball()
	{
		m_color = "black";
		m_radius = 10.0;
	}
 
	// Конструктор только с параметром цвета
    // (радиус будет использовать значение по умолчанию)
	Ball(const std::string &color)
	{
		m_color = color;
		m_radius = 10.0;
	}
 
	// Конструктор только с параметром радиуса
    // (цвет будет использовать значение по умолчанию)
	Ball(double radius)
	{
		m_color = "black";
		m_radius = radius;
	}
 
	// Конструктор с параметрами и цвета, и радиуса
	Ball(const std::string &color, double radius)
	{
		m_color = color;
		m_radius = radius;
	}
 
	void print()
	{
		std::cout << "color: " << m_color << ", radius: " << m_radius << '\n';
	}
};
 
int main()
{
	Ball def;
	def.print();
 
	Ball blue{ "blue" };
	blue.print();
	
	Ball twenty{ 20.0 };
	twenty.print();
	
	Ball blueTwenty{ "blue", 20.0 };
	blueTwenty.print();
 
	return 0;
}

Эта программа должна выдать следующий результат:

color: black, radius: 10
color: blue, radius: 10
color: black, radius: 20
color: blue, radius: 20

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

#include <iostream>
#include <string>

class Ball
{
  private:
    std::string m_color{ "black" };
    double m_radius{}; // Радиус всегда инициализируется конструкторами.
 
  public:
    // Конструктор только с параметром радиуса
    // (цвет будет использовать значение по умолчанию)
    Ball(double radius)
        : m_radius{ radius }
    {
    }
 
    // Конструктор с параметрами и цвета, и радиуса
    Ball(const std::string& color = "black", double radius = 10.0)
        : m_color{ color },
          m_radius{ radius }
    {
    }
 
    void print()
    {
        std::cout << "color: " << m_color << ", radius: " << m_radius << '\n';
    }
};
 
int main()
{
    Ball def{};
    def.print();
 
    Ball blue{ "blue" };
    blue.print();
 
    Ball twenty{ 20.0 };
    twenty.print();
 
    Ball blueTwenty{ "blue", 20.0 };
    blueTwenty.print();
 
    return 0;
}

Вопрос 2

Почему нам не нужно объявлять конструктор без параметров в приведенной выше программе, даже если мы создаем def без аргументов?

Ball def{}; использует второй конструктор Ball, который имеет аргумент по умолчанию для каждого параметра.

Теги

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

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

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