Основы C++: области видимости, связь, имена

Сначала я изучал C++ в университете, но лучше не считать. Затем я впервые начал работать с ним 9 лет назад. Мой работодатель заказал 5-дневный тренинг только для меня. Это были хорошие, щедрые времена. Но я думаю, что обучение не было таким уж успешным по нескольким причинам.

Спустя годы, когда я начал просматривать наши предложения по обучению C++, я понял, что инструктор был ниже среднего. Так же, как и мои знания английского языка и программирования. Несмотря на то, что я долгое время использовал английский язык — даже для работы, — пройти 5-дневный технический тренинг, который проводил неродной преподаватель, было для меня слишком сложно.

Но я учился на ходу.

Более или менее.

Я до сих пор понимаю, что иногда мне не хватает правильного понимания некоторых базовых понятий. Всякий раз, когда я это осознаю, я думаю о том, чтобы написать статью на эту тему. И я пишу статьи каждую неделю на протяжении последних 5 лет.

В последнее время у меня было похожее осознание, когда я читал Beautiful C++. Я бы все равно столкнулся с некоторыми проблемами, если бы мне пришлось объяснять, что такое связь.

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

Что такое имя?

Это кажется простым вопросом, особенно если рассмотреть этот фрагмент кода.

struct S {
  int m_num = 0;
};

int main() {
    [[maybe_unused]] S s{42};
}
Вход в полноэкранный режим Выход из полноэкранного режима

Что такое имя? Это s, верно? Это имя! Ну. Верно. Но в чем разница между именем и объектом?

Это, наверное, еще легко. В чем разница между вашим именем и вами?

Ваше имя обозначает вас, но это не вы, это не ваше физически существующее тело.

Имя — это просто ручка, ссылка на объект.

Это может показаться философским. Тем не менее, важно провести различие. Не только потому, что стандарт C++ делает это, но и потому, что имена и объекты имеют разные атрибуты.

У имен есть область видимости, а у объектов — длительность хранения.

Кроме того, не каждый объект имеет имя, и не каждое имя ссылается на объект. Последнее очевидно. Например, функции и классы также имеют имена, но они не являются объектами.

Объекты могут не иметь имен. Например, временные объекты. Посмотрите на этот пример.

void foo(std::string s) {
  // ...
}

int main() {
  foo(std::string{"bar"});
}
Вход в полноэкранный режим Выход из полноэкранного режима

std::string{"bar"} создает объект, но у него нет имени.

Но давайте вернемся к вопросу о диапазонах и длительности хранения. Начнем с последнего.

Длительность хранения

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

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

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

  • автоматический
  • статический
  • динамический
  • поток

Автоматическая длительность хранения означает, что все хранилище, необходимое для нестатических, невнешних, не потоково-локальных локальных объектов в блоке кода, выделяется в начале блока и деаллоцируется в конце. Это также показывает, как длительность хранения может начинаться раньше, чем время жизни объекта. Обычно хранилище выделяется раньше, чем создается объект.

В случае статической длительности хранения, хранилище выделяется, когда программа начинается, и деаллоцируется, когда программа заканчивается. Переменные со статической длительностью хранения имеют только один экземпляр. Какие объекты имеют статическую длительность хранения? Все, которые были объявлены с ключевым словом static! Кроме того, все объекты, которые были объявлены на уровне пространства имен или объявлены с ключевым словом extern.

Продолжительность динамического хранения, вероятно, вызывает наименьшее количество вопросов. Хранилище для таких объектов выделяется и деаллоцируется по запросу. Подумайте о страшных парах new/delete. Объекты, использующие их, имеют динамическую продолжительность хранения.

И последнее, но не менее важное, что мы должны сказать о длительности хранения локальных переменных потока. Хранилище для таких переменных выделяется при начале потока и деаллоцируется при его завершении. В каждом потоке существует свой экземпляр объекта. Только объекты, объявленные со спецификатором thread_local, имеют такую длительность хранения. thead_local можно комбинировать с ключевыми словами static или extern.

Связь

Теперь, когда мы поговорили об именах и длительности хранения, мы можем наконец поговорить о связывании. Вы объявляете имя в области видимости. Но что произойдет, если вы объявите другую сущность с тем же именем в другой области видимости? Или в нескольких других областях видимости? От связи (отсутствия связи) зависит, сколько экземпляров будет сгенерировано.

До C++20 существовало 3 различных вида связи, четвертый — новый.

  • отсутствие связи
  • внутренняя связь
  • внешняя связь
  • модульная связь (введена в C++20)

Единица трансляции — это базовая единица компиляции в C++. Она состоит из содержимого одного исходного файла, плюс содержимое всех заголовочных файлов, прямо или косвенно включенных в него, минус те строки, которые были проигнорированы с помощью условных операторов предварительной обработки.

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

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

Когда имя имеет внутреннюю связь, на него можно ссылаться из всех областей видимости в текущей единице трансляции. Статические функции, переменные и их шаблонные версии — все они имеют внутреннюю связь. Кроме того, любой имя, объявленное в неименованном пространстве имен, имеет этот уровень связи.

Когда имя имеет внешнюю связь, на него можно ссылаться из области видимости другой единицы перевода. Это может доходить до использования переменных и функций из единиц трансляции, написанных на другом языке. Перечисления, имена классов и их функции-члены и статические члены данных, нестатические шаблоны и шаблоны классов и т.д.

Связь с модулями была введена в C++20. Когда имя имеет модульную привязку, на него можно ссылаться только из той же модульной единицы. Это может означать другую единицу трансляции.

Обратите внимание, что целью этого раздела было показать, какие виды различных связей существуют в C++. Если вы хотите проверить полную спецификацию того, какие имена имеют ту или иную связь, пожалуйста, прочитайте эту страницу.

Область видимости

И последнее, но не менее важное, давайте поговорим о диапазонах. Области — это коллекции имен, относящихся к абстракциям. Область видимости — это место, где имя видно при поиске по неквалифицированному имени. Это подразумевает две вещи:

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

Существует 6 различных областей видимости, о которых мы можем говорить:

  • область видимости блока
  • область параметров функции
  • область пространства имен
  • область действия класса
  • область перечисления
  • область видимости параметров шаблона

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

if (x.isValid) { // opens scope 1
  auto r = 42;
  auto z = Foo{};
  { // opens scope 2!

    auto r = z.something(); // this is a different r

  } // ends scope 2!
  // it's scope 1 again
  std::cout << r << 'n'; // r is 42 once again
} // ends scope 1
Войти в полноэкранный режим Выход из полноэкранного режима

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

Область видимости параметров функции очень похожа на область видимости блока. С точки зрения областей видимости, функция — это комбинация блока и заголовка функции. Функция-попытка-блок аналогична, конец области видимости — это конец последнего блока catch. Кстати, вы когда-нибудь видели блок function-try-block? Следующий фрагмент кода является корректной функцией:

float divide(float a, float b)
try {
  std::cout << "Dividingn";
  return a / b;
} catch (...) {
  std::cout << "Dividing failed, was the denominator zero?n";
}
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Область видимости класса начинается, когда начинается объявление класса, но не заканчивается там, где заканчивается объявление класса. Она просто приостанавливается. Вы можете возобновить ее в любое время, чтобы определить объявленные элементы. После возобновления области видимости вы можете обращаться к элементам класса с помощью различных синтаксисов (., ->, ::).

Область видимости перечисления зависит от типа перечисления. В любом случае, область видимости начинается с начала объявления перечисления. Область видимости скопированного enum заканчивается в конце объявления. Но область видимости нескопированного enum заканчивается в конце объемлющей области видимости.

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

Заключение

В этой статье мы обсудили несколько идей, которые часто используются, когда люди говорят о C++ или программировании в целом. Слова, которые мы, возможно, не решаемся использовать в повседневных разговорах, потому что не уверены, что хорошо их понимаем. Имена, области видимости, связь и даже единицы трансляции! Сегодня мы на несколько шагов приблизились к тому, чтобы лучше их понимать.

Я настоятельно рекомендую вам ознакомиться со связанными материалами, чтобы понять их глубже!

Подключайтесь глубже

Если вам понравилась эта статья, пожалуйста

  • нажмите на кнопку «Мне нравится»,
  • подпишитесь на мою рассылку
  • и давайте общаться в Twitter!

Оцените статью
Procodings.ru
Добавить комментарий