Оператор using

Практический курс по C/C++: https://stepik.org/course/193691

На прошлых занятиях для обращения к элементам пространства имен std нам приходилось прописывать std::cin, std::cout и так далее. Это не очень удобно. Если в модуле (файле) предполагается постоянное использование каких-либо элементов из того или иного пространства имен, то эти элементы можно импортировать непосредственно в этот модуль. В частности, для этого предназначен оператор using. Использовать его можно в соответствии со следующим синтаксисом:

using <пространство имен>::<элемент>;

Например, чтобы импортировать объекты cin/cout и функцию endl в текущий модуль в глобальное пространство имен, достаточно прописать:

#include <iostream>
 
using std::cout;
using std::cin;
using std::endl;
 
int main()
{
    char str[50];
 
    cin >> str;
    cout << "Hello, " << str << "!" << endl;
 
    return 0;
}

Если же этот оператор прописать в каком-либо блоке, например, в теле функции main(), то импорт будет сделан в пределах этого блока:

#include <iostream>
 
int main()
{
    using std::cout;
    using std::cin;
    using std::endl;
 
    char str[50];
 
    cin >> str;
    cout << "Hello, " << str << "!" << endl;
 
    return 0;
}

Соответственно, определения cout, cin и endl доступны теперь только внутри тела функции main() и не доступны за его пределами.

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

Давайте посмотрим, как синтаксически выполняется импорт всего пространства. Для этого также прописывается ключевое слово using, за которым следует еще одно ключевое слово namespace с указанием имени импортируемого пространства. Например:

int main()
{
    using namespace std;
    // using std::cout;
    // using std::cin;
    // using std::endl;
 
    char str[50];
 
    cin >> str;
    cout << "Hello, " << str << "!" << endl;
 
    return 0;
}

В результате, все определения из std становятся доступными напрямую в теле функции main(). И, еще раз, обратите внимание. Писать подобный импорт в глобальной области – крайне плохая практика:

#include <iostream>
 
using namespace std;
 
int main()
{
    char str[50];
 
    cin >> str;
    cout << "Hello, " << str << "!" << endl;
 
    return 0;
}

Часто, далеко не все, что прописано в std, используется в текущем модуле. И программист вполне может случайно объявить переменную или функцию или что-либо еще с тем же именем, что и импортированный элемент. Возникнет конфликт имен и компиляция программы завершится с ошибкой. Как раз чтобы этого избежать, лучше явно указывать импортируемые элементы:

using std::cout;
using std::cin;
using std::endl;

Или, в крайнем случае, делать полный импорт в локальную область видимости.

Определение псевдонимов типов

Но это еще не все, что умеет делать оператор using. С его помощью можно создавать псевдонимы (алиасы) существующих в программе типов данных. Делается это по следующему синтаксису:

using <alias> = <тип данных>;

Например, в самом простом варианте, можно прописать что-то вроде:

using byte_8 = unsigned char;

В программе появляется новое имя byte_8 базового типа unsigned char, которое полноценно можно использовать следующим образом:

byte_8 byte;
byte_8* byte_ptr;

Или, такой пример. Пусть у нас в программе объявлено пространство имен с определением функции и структуры:

namespace firstSpace {
    void foo()
    {
        cout << "function from firstSpace: foo()" << endl;
    }
 
    struct point {
        double x, y;
    };
}

Тогда для типа firstSpace::point можно создать псевдоним следующим образом:

using point2D = firstSpace::point;

и использовать его для объявления соответствующей переменной на структуру:

point2D pt;

При этом прежний тип firstSpace::point, конечно же продолжает существовать. Мы лишь создаем еще одно имя этого типа не более того. И using создает псевдонимы именно для типов данных. Например, запись вида:

using func = firstSpace::foo;

приведет к ошибке, т.к. foo – это имя функции, а не тип данных.

Вообще конструкция

using <alias> = <тип данных>;

очень напоминает оператор typedef языка Си. Например, с его помощью мы также можем записать:

typedef unsigned char byte_8;
typedef firstSpace::point point2D;

На первый взгляд никаких отличий. Но они все же имеются. Оператор using полностью покрывает функциональность оператора typedef и привносит некоторые дополнительные возможности и улучшения. Например, объявление типа указателя на функцию через typedef выглядит так:

typedef float (*func_ptr)(int);

а с использованием using несколько понятнее и красивее:

using func_ptr = float (*)(int);

Но, конечно, главное преимущество using перед typedef проявляются при работе с шаблонами (templates). Но это уже выходит за пределы нашего базового курса по С++. Отмечу лишь, что в современных программах на С++ нет смысла использовать typedef и от него лучше отказываться в пользу оператора using. Хотя это не строгое правило и применение typedef все же допустимо.

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме