Обязательное размещение порядка деклараций

Скоро наступит середина 2022 года, а значит, мы все ближе и ближе к C++23. Я планирую показывать вам все больше новых возможностей и исправлений из грядущей версии. Первым из них был вывод, а сегодня мы продолжим его статьей Пэла Балога о том, как сделать расположение порядка деклараций обязательным.

Что мы подразумеваем под (стандартным) макетом?

Когда мы говорим о компоновке класса (в C++), мы имеем в виду, как он представлен в памяти, где и в каком порядке хранятся различные поля.

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

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

  • Все нестатические члены данных имеют одинаковый контроль доступа.
  • Не имеет виртуальных функций или виртуальных базовых классов
  • Не имеет нестатических членов данных ссылочного типа
  • Все нестатические члены данных и базовые классы сами являются стандартными типами компоновки
  • Не имеет двух (возможно, косвенных) подобъектов базового класса одного и того же типа
  • Все нестатические члены данных и битовые поля объявлены в одном классе (либо все в производном, либо все в каком-то базовом)
  • Ни один из подобъектов базового класса не имеет тот же тип, что и… для бессоюзных типов, как первый нестатический член данных (см. оптимизация пустой базы), и, рекурсивно, первый нестатический член данных этого члена данных, если он имеет тип бессоюзного класса, или все нестатические члены данных этого члена данных, если он имеет тип союза, или элемент этого члена данных, если он имеет тип массива, и т.д.— для типов объединения, как любые нестатические члены данных, и, рекурсивно, первый нестатический член данных каждого члена не объединенного типа класса, и все нестатические члены данных всех членов типа объединения, и тип элемента всех нестатических членов данных типа массива, и т.д.— для типов массивов, как тип элемента массива, и, рекурсивно, как первый нестатический член данных элемента массива, если он имеет не союзный тип класса, или как любой нестатический член данных элемента массива, если он имеет союзный тип, или как тип элемента массива, если он имеет тип массива, и т.д.

Это довольно длинный список. Если вы хотите легко проверить, имеет ли ваш класс стандартную компоновку или нет, вы можете использовать std::is_standard_layout.

#include <iostream>
#include <type_traits>

class A {
  int a;
  int b;
};

class B {
  int a;
public:
  int b;
};

class C {
  C (int& ib) : b(ib) {}
  int a;
  int& b;
};


int main() {
  std::cout << std::boolalpha;
  std::cout << std::is_standard_layout_v<A> << 'n';
  std::cout << std::is_standard_layout_v<B> << 'n';
  std::cout << std::is_standard_layout_v<C> << 'n';
}
Вход в полноэкранный режим Выход из полноэкранного режима

Так о чем же идет речь в документе?

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

Допустим, у вас есть класс MyType.

class MyType {
public:
  int m_a;
private:
  int m_b;
  int m_c;
public:
  int m_d;
};
Войти в полноэкранный режим Выход из полноэкранного режима

Компиляторы могут решить присвоить m_b и m_c более низкий адрес, чем m_a. Хотя они не могут изменить порядок между m_b и m_c и даже между m_a и m_d. По крайней мере, не с C++11. В C++03 m_d мог предшествовать m_a в макете, поскольку они были частью двух разных блоков управления доступом.

Старое правило из C++03 гласило, что «нестатические члены данных (несоюзного) класса, объявленные без промежуточного спецификатора доступа, распределяются так, чтобы последующие члены имели более высокие адреса внутри объекта класса. Порядок выделения нестатических членов данных, разделенных спецификатором доступа, не определен (11.1)».

Позже, в C++11, N2342 были внесены некоторые изменения, чтобы урезать уровень свободы исполнителей. «Требование, чтобы члены данных POD не имели промежуточных спецификаторов доступа, изменено на требование только того, чтобы такие члены данных имели одинаковый контроль доступа. Считается, что это изменение также больше соответствует ожиданиям программистов, чем текущие требования».

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

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

Таким образом, если MyType подвержен переупорядочиванию членов до C++20, то начиная с C++23 это больше не будет возможно.

Заключение

P1847R4 объясняет, как макеты могут быть переупорядочены в C++ при смешанном управлении доступом, и предлагает устранить возможность такого переупорядочения. Хотя это не меняет стандартную компоновку, оно удаляет правило, которое не использовалось и казалось довольно произвольным.

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

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

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

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