Программирование на C и C++

Онлайн справочник программиста на C и C++

Создание операторов вставки

В С++ имеется легкий способ определения оператора вставки для создаваемых классов. В следую­щем простом примере создается оператор вставки для класса three_d:

class three_d {
public:
int x, у, z; // трехмерные координаты
three_d(int a, int b, int c) { x=a; y=b; z=c; }
};

Для того, чтобы определить оператор вставки для объекта типа three_d, необходимо опреде­лить этот оператор по отношению к классу three_d. Для этого необходимо перегрузить оператор <<. Один из способов показан ниже.

// вывод координат X, Y, Z (оператор вставки для three_d)
ostream &operator<<(ostream &stream, three_d obj)
{
stream << obj.x << ", ";
stream << ob j.у << ", ";
stream << ob j.z << "\n";
return stream; // возврат потока
}

Многие особенности этой функции являются общими для всех функций вставки. Прежде всего обратим внимание, что в качестве возвращаемого значения этой функции объявлен объект типа ostream. Это необходимо для того, чтобы один оператор мог содержать несколько операторов вставки. Далее, функция имеет два параметра. Первым служит ссылка на поток, который фигури­рует в левой части оператора <<. Вторым параметром служит объект с правой стороны операто­ра <<. В самой функции осуществляется вывод трех величин, содержащихся в объекте типа three_d, после чего возвращается поток stream. Следующая короткая программа служит для демонстра­ции оператора вставки:

#include <iostream.h>
class three_d {
public:
int x, y, z; // трехмерные координаты
three_d(int a, int b, int c) { x=a; y=b; z=c; }
};
// вывод координат X, Y, Z (оператор вставки для three_d)
ostream &operator<<(ostream &stream, three_d obj)
{
stream << obj.x << ", ";
stream << obj.у << ", ";
stream << obj.z << "\n";
return stream; // возврат потока
}
int main()
{
three_d a(1, 2, 3), b(3, 4, 5), c(5, 6, 7);
cout << a << b << c;
return 0;
}

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

ostream &operator<<(ostream &поток, тип_класса объект)
{
// характерный для типа код
return stream; // возврат потока
}

Остается только определить конкретные действия, выполняемые таким оператором вставки. Об­ратим внимание на необходимость возвращать stream. Также является приемлемым и, более того, является общей практикой использовать в качестве параметра объект ссылку, а не сам объект. Преимуществом передачи ссылки на объект служит то, что если объект является большим, то гораздо быстрее передать его адрес. Во-вторых, это предотвращает вызов деструктора объекта когда функция вставки возвращает результат.

Кажется странным, почему функция вставки объекта типа three_d не реализована в коде напо­добие следующего:

// ограниченная версия - не использовать
ostream &operator<<(ostream &stream, three_d obj)
{
cout << obj.x << ", ";
cout << obj.у << ", ";
cout << obj.z << "\n";
return stream; // возврат потока
}

В данной версии поток cout вставлен в тело функции, однако надо иметь в виду, что оператор << может применяться к любому потоку. Поэтому необходимо использовать поток, передаваемый функции, для того, чтобы она могла корректно работать во всех случаях.

В программе вставки класса three_d перегруженная функция вставки не является членом класса three_d. Действительно, ни функция вставки, ни функция извлечения не могут быть членами клас­са. Причина заключается в том, что если функция-оператор является членом класса, то левым операндом, неявно передаваемым с использованием указателя this, служит объект того класса, который осуществляет вызов функции-оператора. И нет способа изменить такой порядок. Вместе с тем при перегрузке оператора вставки левым аргументом является поток, а правым аргументом — объект класса. Поэтому перегруженные операторы вставки не могут быть функциями-членами. Тот факт, что функции вставки не могут быть членами класса для действия, на котором они определены, вызывает серьезные вопросы: как перегруженный оператор вставки может получить доступ к частным членам класса? В предыдущей программе переменные х, у и z были публичны­ми, так что оператор вставки имел к ним доступ. Однако защита данных является одной из важ­нейших особенностей объектно-ориентированного программирования, и требовать, чтобы все данные были публичными, значит противоречить самому духу объектно-ориентированного про­граммирования. Тем не менее данная проблема имеет решение: оператор вставки может быть другом класса. В качестве друга класса, для которого он определен, он имеет доступ к частным данным. Иллюстрация этого имеется в следующем примере:

#include <iostream.h>
class three_d {
int x, у, z; // трехмерные координаты - теперь частные
public:
three_d(int a, int b, int c) { x=a; y=b; z=c; }
friend ostream &operator<<(ostream &stream, three_d obj);
};
// вывод координат X, Y, Z (оператор вставки для three_d)
ostream &operator<<(ostream &stream, three_d obj)
{
stream << obj.x << ", ";
stream << obj.у << ", ";
stream << obj.z << "\n";
return stream; // возврат потока
}
int main() {
three_d a(1, 2, 3), b(3, 4, 5), с (5, 6, 7);
cout << a << b << c;
return 0;
}

Обратим внимание, что переменные х, у и z являются в данном случае частными членами класса three_d, но тем не менее продолжают быть доступными непосредственно с помощью функции вставки. Объявление операторов вставки и извлечение друзьями классов, для которых они опре­делены, позволяет сохранить неприкосновенным принцип инкапсуляции объектно-ориентированного программирования.