STM32, SystemInit и FPU
Допустим, у нас таки есть плата для разработки. Что-то вроде nucleo-stm32l4 и чего-то там еще. Как только берешь ее в руки, то появляется естественное желание сгенерировать начальный проект в фирменном приложении STM32Cube. Почему нет, это удобно для быстрого старта и новичка. Настроим проект под нашу платку и получим стартовый код специально для IAR:
Нас больше интересует вторая часть, где настраивается FPU. В прошлый раз мы убедились, что IAR может настроить все самостоятельно. Однако, ребята из ST предпочитают не верить ребятам из IAR Systems на слово и делают свою инициализацию.
Тут мы явно берем регистр
🔹
🔹 определен
То есть, если не мухлевать с препроцессором, то условие
Допустим, у нас таки есть плата для разработки. Что-то вроде nucleo-stm32l4 и чего-то там еще. Как только берешь ее в руки, то появляется естественное желание сгенерировать начальный проект в фирменном приложении STM32Cube. Почему нет, это удобно для быстрого старта и новичка. Настроим проект под нашу платку и получим стартовый код специально для IAR:
Reset_HandlerТут генератор выдает нам обработчик сброса
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
Reset_Handler
, в нем только вызовы SystemInit
и __iar_program_start
. Со второй функцией все понятно, а что находится в SystemInit
?void SystemInit(void) {Для нашего случая функция получилась весьма лапидарной. Первая часть говорит о том, что если вы перенесли куда-то вектор прерываний, например, в оперативную память, то не забудьте известить об этом регистр
#if defined(USER_VECT_TAB_ADDRESS)
/* Configure the Vector Table location*/
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
#endif
/* FPU settings*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 20U)|(3UL << 22U)); /* set CP10 and CP11 Full Access */
#endif
}
VTOR
(Vector table offset register из клана SCB
(System control block).Нас больше интересует вторая часть, где настраивается FPU. В прошлый раз мы убедились, что IAR может настроить все самостоятельно. Однако, ребята из ST предпочитают не верить ребятам из IAR Systems на слово и делают свою инициализацию.
Тут мы явно берем регистр
CPACR
(Coprocessor Access Control Register) за адрес 0xE000ED88
и нежно впихиваем единицы в биты 20, 21, 22 и 23. Да, это все те же самые действия, что и в функции __iar_init_vfp
из стандартной инициализации. Забавно, что и происходит она в тех же случаях: __FPU_PRESENT
определяется в заголовочных файлах для используемого семейства контроллеров (для данного случая stm32l452xx.h). В общем-то логично, на этом этапе мы точно знаем, есть ли FPU или нет. __FPU_USED
определяется в заголовочном файле для конкретной архитектуры процессора, и это дефайн будет определен как единица, только если:🔹
__FPU_PRESENT
равен единице🔹 определен
__ARMVFP__
, который поставляется IAR компилятором, если использован флаг --fpu
со значением, отличным от none
.То есть, если не мухлевать с препроцессором, то условие
(__FPU_PRESENT == 1) && (__FPU_USED == 1)
избыточно и тяжеловато. Но, как говорил великий гуманитарный мыслитель современности, тяжелый - значит надежный.👍9
AUTOSAR
Правило A8-5-3
Переменные типа auto не должны быть инициализированы через {} или ={}.
Наш любимый стандарт не запрещает использовать тип auto, но смотрит на него большим с подозрением. Не во всех случаях его можно использовать, но где можно, там нужно правильно инициализировать переменную. Если будете использовать фигурные скобочки, то можете получить, к своему изумлению, совсем не тот тип, на который рассчитывали. А уж если применили
Тут же оговоримся, что некоторые компиляторы (не будем показывать пальцем, хотя это GCC и Clang) реализовывали инициализацию
Действительно, если использовать только скобки, то в GCC тип выведется правильно.
Все изменило предложения N3922 "New Rules for auto deduction from braced-init-list", и после можно наблюдать благостную картину единообразия во всех компиляторах. Вот такие примеры есть в документе:
Просто напомню, что
Можно еще точно вывести тип как
выведенный тип
Все эти ухищрения, может, и хорошее дело, но некоторые анализаторы кода считают это грубым нарушением правила.
Правило A8-5-3
Переменные типа auto не должны быть инициализированы через {} или ={}.
Наш любимый стандарт не запрещает использовать тип auto, но смотрит на него большим с подозрением. Не во всех случаях его можно использовать, но где можно, там нужно правильно инициализировать переменную. Если будете использовать фигурные скобочки, то можете получить, к своему изумлению, совсем не тот тип, на который рассчитывали. А уж если применили
={}
, то уж точно в итоге вас ждет тип std::initializer_list
.Тут же оговоримся, что некоторые компиляторы (не будем показывать пальцем, хотя это GCC и Clang) реализовывали инициализацию
auto
как им вздумается (до c++17).Действительно, если использовать только скобки, то в GCC тип выведется правильно.
struct S { int x; };До с++17 так могло и не быть, этот важный момент не регламентировался.
S get() { return S{0}; }
auto x {get()}; // decltype(x) == S
auto y = {get()}; // decltype(y) == std::initializer_list<S>
Все изменило предложения N3922 "New Rules for auto deduction from braced-init-list", и после можно наблюдать благостную картину единообразия во всех компиляторах. Вот такие примеры есть в документе:
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>А еще недавно
auto x2 = { 1, 2.0 }; // error: cannot deduce element type
auto x3{ 1, 2 }; // error: not a single element
auto x4 = { 3 }; // decltype(x4) is std::initializer_list<int>
auto x5{ 3 }; // decltype(x5) is int
x3
прекрасно бы выводился в std::initializer_list<int>
, как и x5
.Просто напомню, что
auto
может быть щедро приправлен модификаторами, вроде const
или &
. Тогда тип выводится согласно правилу для аргумента шаблона из вызова функции.const auto& x = get();Если бы вызов функции
template<class T> void f(const T& arg);
f(get())
был скомпилирован, то типы x
и arg
совпали бы.Можно еще точно вывести тип как
decltype(auto) x = get();
выведенный тип
x
это decltype(get())
.Все эти ухищрения, может, и хорошее дело, но некоторые анализаторы кода считают это грубым нарушением правила.
👍8
AUTOSAR
Правило A7-1-5
Спецификатор auto может быть использован только:
1. объявления переменной такого же типа, что и возвращаемый тип вызываемой функции
2. объявления переменной такого же типа, что и инициализатор нефундаментального типа
3. объявления параметров обобщенного лямбда-выражения
4. объявления шаблонной функции, если используется вывод типа возвращаемого значения
В очередной раз убеждаемся, что наш любимый стандарт не переваривает
Например, так инициализировать переменную строжайше запрещено:
Как тип аргумента auto тоже можно использовать в обобщенном лямбда-выражении.
Впрочем, наш анализатор никаких auto не признавал, при любом удобном случае жалуясь на нарушение одного из правил инициализации переменных. Лучший способ избежать бессмысленной борьбы с шайтан-машиной - не использовать этот спецификатор вообще. Хотя это скучно.
Правило A7-1-5
Спецификатор auto может быть использован только:
1. объявления переменной такого же типа, что и возвращаемый тип вызываемой функции
2. объявления переменной такого же типа, что и инициализатор нефундаментального типа
3. объявления параметров обобщенного лямбда-выражения
4. объявления шаблонной функции, если используется вывод типа возвращаемого значения
В очередной раз убеждаемся, что наш любимый стандарт не переваривает
auto
и согласен его терпеть только в крайних случаях. Аргументы нам хорошо знакомы: выведенный тип может отличаться от задуманного и оконфузит разработчика перед нормальными пацанами или резко сделает код таким же читаемым, как повесть о Гэ́ндзи на языке оригинала.Например, так инициализировать переменную строжайше запрещено:
auto x = 42;Тип
x
будет выведен как int
, а точно ли мы хотели этого? Может, нам нужен int16_t
? Лучше указать явно.auto x = static_cast<uint16_t>(42);Нет, только не так, пожалуйста! Все равно
uint16_t
фундаментальный тип, и auto
здесь неприменим.struct A {};А вот так применять спецификатор можно, хотя это какой-то разврат. Уж лучше так:
auto a = A{};
A a{};Вот уж где сложно обойтись без
auto
, так это в объявлении лямбда-функций. Они имеют уникальный тип, поэтому сложно записать это как-то иначе. auto lambda = []() -> std::int16_t {Можно было бы использовать
return 42U;
};
std::function
, но это типичное не то. Использование стандартной обертки для функций без нужды только утяжелит код.Как тип аргумента auto тоже можно использовать в обобщенном лямбда-выражении.
auto lambda = [](auto x, auto y) -> decltype(x + y) ...Выводить тип таким образом тоже вполне законно, мы тут намекаем, что согласны на любой, который возвращает функция.
auto x = lambda(1.F, 1);Ну и конечно, шаблонные функции:
template <typename T, typename U>Если в возвращаемом типе каким-либо образом участвует шаблонный тип, то должен использоваться вывод типа возвращаемого значения. Если же шаблонная функция возвращает какой-нибудь простецкий
auto F(T t, U u) noexcept -> decltype(t + u);
size_t
, то это вовсе необязательно.Впрочем, наш анализатор никаких auto не признавал, при любом удобном случае жалуясь на нарушение одного из правил инициализации переменных. Лучший способ избежать бессмысленной борьбы с шайтан-машиной - не использовать этот спецификатор вообще. Хотя это скучно.
👍8
decltype
На страницах cppreference, посвященных объявлению функций (Function declaration), можно заметить странную вещь:
Однако на самом деле
Как всегда в непонятной ситуации не будем притворяться мертвыми, а немедленно обратимся к стандарту. В пункт 4 раздела 7.1.6.2 Simple type specifiers [dcl.type.simple] есть кое-что интересное.
Выводимый для выражения
- если
- иначе, если
- иначе, если
- во всех других случаях
То есть, грубо говоря,
Случай
Не ожидал, конечно, от обычных скобочек такого коварства.
На страницах cppreference, посвященных объявлению функций (Function declaration), можно заметить странную вещь:
int x = 1;Хочется воскликнуть: "Почему так?!". Интуитивно не очень понятен этот кунштюк.
decltype(auto) f() { return x; } // return type is int
decltype(auto) f() { return(x); } // return type is int&
Однако на самом деле
decltype(x)
и decltype((x))
дают разные типы: int
и int&
соответственно.Как всегда в непонятной ситуации не будем притворяться мертвыми, а немедленно обратимся к стандарту. В пункт 4 раздела 7.1.6.2 Simple type specifiers [dcl.type.simple] есть кое-что интересное.
Выводимый для выражения
e
тип определяется следующим образом:- если
e
это имя или член класса не заключенное в скобки, то decltype(e)
выводит тип сущности названной e
. Если такой сущности нет или она отсылает к набору перегруженных функций, то у меня для вас плохие новости.- иначе, если
e
это xvalue
(это то, что возвращает std::move
, например), decltype(e)
выводит тип T&&
, где T
это тип e
.- иначе, если
e
это lvalue
, то decltype(e)
выводит тип T&
, где T
это тип e
.- во всех других случаях
decltype(e)
без затей выводит тип e
.То есть, грубо говоря,
decltype
может быть использован для разных целей в совершенно разных контекстах: вывод типа сущности и проверка типа выражения (decltype(identifier)
и decltype(expression)
). Наш (x)
это хоть элементарное, но выражение, а не имя переменной.Случай
decltype(x)
подпадает под первый пункт правила, тип x
- int
, он же и будет выведен.decltype((x))
относится уже к третьему пункту, поэтому получим тип T&
, где T
это int
.Не ожидал, конечно, от обычных скобочек такого коварства.
👍7😁1
std::equal
Забавную историю по-секрету рассказал мне товарищ, близкий друг подружки девушки брата которого реализовал свой контейнер с блэкджеком и луна-парком для хитрого хранения или нетривиальной обработки каких-то там данных с датчиков устройства. Написал вроде неплохо, про тестирование не забыл, красивые модульные google test-ы радовали не один глаз. Однако коварный баг не дремлет и таки прокрался незамеченным. В итоге, девайс выгорел дотла. Может, и не само устройство, вероятно, это разработчик выгорал... или угорал? Тут я не очень понял, но было жарко.
Причину, к счастью, нашли, безобразия прекратились, и ментальное здоровье сотрудника было спасено. Первоисточником всех злоключений послужил некорректный, неспособный провалиться тест. Допустим, у нас есть с большой любовью написанный контейнер
Примечательно, что все слезные мольбы добавить в gtest сравнение контейнеров разбивается о недоумение авторов данного фреймворка: "У вас уже все есть!".
Забавную историю по-секрету рассказал мне товарищ, близкий друг подружки девушки брата которого реализовал свой контейнер с блэкджеком и луна-парком для хитрого хранения или нетривиальной обработки каких-то там данных с датчиков устройства. Написал вроде неплохо, про тестирование не забыл, красивые модульные google test-ы радовали не один глаз. Однако коварный баг не дремлет и таки прокрался незамеченным. В итоге, девайс выгорел дотла. Может, и не само устройство, вероятно, это разработчик выгорал... или угорал? Тут я не очень понял, но было жарко.
Причину, к счастью, нашли, безобразия прекратились, и ментальное здоровье сотрудника было спасено. Первоисточником всех злоключений послужил некорректный, неспособный провалиться тест. Допустим, у нас есть с большой любовью написанный контейнер
Numbers
со всеми свойствами, присущими стандартным коллекциям. Для наглядности можем определить его как вектор:using Numbers = std::vector<int>;Теперь не без отвращения взглянем на тест:
Numbers data = produce_data();На первый взгляд, все в порядке. Функция
EXPECT_TRUE(std::equal(data.begin(), data.end(), expected_data.begin()));
produce_data
исполняет некоторые телодвижения, и в data
возвращается определенная числовая последовательность. (Фейковые данные от датчиков, например). Потом сравниваем эту последовательность с ожидаемой. Нет сомнений, что функция std::equal
из стандартной библиотеки сделает сравнение корректно. Самое интересное началось, когда produce_data
сломался и стал возвращать пустой контейнер. Как ни странно, но сравнение пустоты и контейнера с ожидаемыми данными возвращает true
. Хотя почему странно? Никакой ошибки тут нет. Если вызывать std::equal с тремя аргументамиtemplate< class InputIt1, class InputIt2 >нельзя делать предположения о размере второй коллекции, эта проверка будет опущена. Можно улучшить ситуацию, поменяв местами контейнеры
bool equal( InputIt1 first1, InputIt1 last1,
InputIt2 first2 );
EXPECT_TRUE(std::equal(expected_data.begin(), expected_data.end(), data.begin()));Хотя лучше использовать версию сравнения с 4 аргументами, так надежнее.
EXPECT_TRUE(std::equal(expected_data.begin(), expected_data.end(), data.begin(), data.end()));А еще лучше вообще не трогать функции, в которых можно запутаться.
Примечательно, что все слезные мольбы добавить в gtest сравнение контейнеров разбивается о недоумение авторов данного фреймворка: "У вас уже все есть!".
ASSERT_THAT(data, testing::ElementsAre(0, 1, 2, 3, 4));Вот это они предлагают использовать. Более того, можно сравнивать не точную последовательность, а диапазон значений.
ASSERT_THAT(data, testing::Each(testing::AllOf(testing::Gt(-10), testing::Lt(10))));Это еще не все возможности, которые можно задействовать для безопасного и изящного тестирования...
👍11
Media is too big
VIEW IN TELEGRAM
"C++ lambda idioms" Timur Doumler
Наконец-то начали появляться видео с CppCon 2022! Теперь можно пересмотреть доклады, которые были на C++ Russia и на CppNorth. Почему бы нет, если материал хороший? Как вы уже догадались, речь про Тимура и его С++ лямбда идиомы. Тут все, что вы хотели знать о лямбдах, но боялись спросить. Например, про волшебное слово
Можно на самом деле:
auto x = [] [[nodiscard]] (...) {...};
Также докладчик популярно объяснит, почему это не скомпилируется:
auto *ptr = +[](int) -> void {};
Немного информации про железную хватку лямбды, и почему иногда она не работает. Еще лямбды, которые вызываются немедленно, те, что вызываются единственный раз, как вызвать лямбду рекурсивно, собрать из палок и лямбд простой
Наконец-то начали появляться видео с CppCon 2022! Теперь можно пересмотреть доклады, которые были на C++ Russia и на CppNorth. Почему бы нет, если материал хороший? Как вы уже догадались, речь про Тимура и его С++ лямбда идиомы. Тут все, что вы хотели знать о лямбдах, но боялись спросить. Например, про волшебное слово
mutable
в объявлении лямбд, или можно ли использовать атрибут [[nodiscard]]
.auto x = [] [[nodiscard]] (...) {...};
auto x = []{};Как обмануть компилятор и использовать вывод типа с приведением лямбды к указателю на функцию!
x = decltype(x){}; // error
x = x; // error
variant
.👍6
GTest & MATCHER
Иногда стандартные средства google test огорчают нас тем, что не умеют работать с нашими велосипедосодержащими типами данных или манкируют указателями. Например, к ним оказывается не совсем применим замечательный совет разработчиков использовать
Некоторые товарищи советуют организовать какой-нибудь стандартный контейнер вокруг указателя, так сказать, загнать вольный дух незамутненного си в богомерзкий
Как говорил Маяковский, цитируя Ленина: "Мы пойдем путем другим!". Нужно всего-то сделать собственный matcher. Это такая штука, что помогает проверять различные объекты при тестировании. Для уменьшения количества страданий и легкого создания собственных функций сравнения, мудрые отцы-основатели подготовили для нас целое семейство макросов
Для начала опробуем matcher с одним параметром:
Так мы передаем
Иногда стандартные средства google test огорчают нас тем, что не умеют работать с нашими велосипедосодержащими типами данных или манкируют указателями. Например, к ним оказывается не совсем применим замечательный совет разработчиков использовать
ASSERT_THAT
и ElementsAre
. Это даже не компилируется.Некоторые товарищи советуют организовать какой-нибудь стандартный контейнер вокруг указателя, так сказать, загнать вольный дух незамутненного си в богомерзкий
std::vector
.Как говорил Маяковский, цитируя Ленина: "Мы пойдем путем другим!". Нужно всего-то сделать собственный matcher. Это такая штука, что помогает проверять различные объекты при тестировании. Для уменьшения количества страданий и легкого создания собственных функций сравнения, мудрые отцы-основатели подготовили для нас целое семейство макросов
MATCHER*
.Для начала опробуем matcher с одним параметром:
MATCHER_P(RawPointerCmp, list, "RawPointer") {Всю грязную работу за нас сделает макрос
bool result {true};
size_t i {};
for (auto &&x : list) {
result = result && (arg[i++] == x);
}
return result;
}
MATCHER_P
. Нам остается задать имя функции, аргумента и пространное описание, которое все игнорируют. Здесь list
- это ожидаемые данные, мы предполагаем, что они хранятся в каком-то контейнере. Внезапно появившийся внутри arg
- это подлежащий проверке указатель, он передается как первый аргумент EXPECT_THAT
(или ASSERT_THAT
).Так мы передаем
ptr
как arg
в наш новый matcher вместе с эталонными значениями в std::initializer_list
:ASSERT_THAT(ptr, RawPointerCmp(std::initializer_list<int>{0, 1, 2, 3, 4}));В общем, туда же можно передать больше параметров (по слухам, до 10), надо только изменить имя макроса на
MATCHER_P2
и добавить сами аргументы.MATCHER_P2(RawPointerCmp2, arr, size, "RawPointer2") {Вот так это можно использовать, если вдруг использование
bool result {true};
for (int i {}; i < size; i++) {
result = result && (arg[i] == arr[i]);
}
return result;
}
ASSERT_THAT(ptr, RawPointerCmp2(raw, 5));
memcmp
в тестах кажется вам недостаточно куртуазным.👍7
std::array<T, 0>
Представим, что у нас есть некий контейнер. У него есть метод
В
Все потому, что внутри использован тот же грязный прием. Для получения значений
Представим, что у нас есть некий контейнер. У него есть метод
front
, возвращающий первое значение в коллекции. На первый взгляд, реализовать это не представляется никакой сложности. Однако дьявол в деталях. Что делать, если значений не завезли? В
std::vector
используется разновидность assert
, которая требует, чтоб контейнер не был пуст.__glibcxx_requires_nonempty();Что на самом деле значит:
#define __glibcxx_requires_nonempty() \Части вашей команды может не понравиться такое решение, поскольку это якобы маскирует ошибки. Исключения были исключены другими коллегами сразу из-за избыточности и тяжеловесности. Когда немало копий уже поломано, на свет появляются довольно странные решения вроде:
__glibcxx_assert(__builtin_expect(!this->empty(), true))
template <class T>Понимаю, некоторые сейчас ощутили рвотный позыв, но подождите кидать гневные комментарии на код-ревью. Да, когда контейнер пуст,
struct Bucket {
...
T &front() {
if (empty()) {
return *static_cast<T*>(nullptr);
}
return bucket_[0];
}
};
front
вернет nullptr
в виде ссылки. Попытка разыменовать это приведет к Segmentation fault. Обратимся снова к стандартной библиотеке. std::array
не использует никаких проверок, но есть особый случай для массива с нулевым размером.std::array<int, 0> c{};Любая попытка извлечь значение закончится звонким крушением надежд.
c[0] = 0; // Segmentation fault
c.front(); // Segmentation fault
Все потому, что внутри использован тот же грязный прием. Для получения значений
array
обращается к вспомогательной структуре __array_traits
:template<typename _Tp>Так можно ли осуждать бедного разработчика, если такой же подход используется в стандартной библиотеке в исполнении GCC? Теперь можно! В последней версии GCC 13 попытка получить доступ к элементам массива нулевого размера приведет к Illegal instruction, что необычно. Заглянем немного внутрь:
struct __array_traits<_Tp, 0>
{
struct _Type { };
...
static constexpr _Tp& _S_ref(const _Type&, std::size_t) noexcept
{ return *static_cast<_Tp*>(nullptr); }
__attribute__((__always_inline__,__noreturn__))Теперь вместо вспомогательной функции
_Tp& operator[](size_t) const noexcept { __builtin_trap(); }
_S_refиспользуется
operator[]
, а __builtin_trap()
генерирует в этом месте уже знакомую нам инструкцию ud2
, что провоцирует серьезную ошибку или "жесточайшее исключение".👍9
Gtest & custom assertion
Вы жестоко ошиблись, если подумали, что все странные способы сравнения в Google Test исчерпаны. Можно же сделать собственные утверждения, вроде
Да, это макрос, заключающий в себе еще один специальный макрос.
Специальный макрос GTest рассчитывает, что первыми аргументами идут строковые описания данных, а затем и сами данные. То есть если мы напишем сравнение
Утверждение должно вернуть результат типа
Можно сделать смелое утверждение, что такой же макрос и функцию сравнения легко написать для массивов в си-стиле
но проверять его мы, конечно же, не будем.
Вы жестоко ошиблись, если подумали, что все странные способы сравнения в Google Test исчерпаны. Можно же сделать собственные утверждения, вроде
EXPECT_EQ
, только жаркие и зимние. Что-то вроде такого:#define EXPECT_CONTAINER_EQ(exp, data) EXPECT_PRED_FORMAT2(AssertContainerEquals, (exp), (data));
Да, это макрос, заключающий в себе еще один специальный макрос.
EXPECT_PRED_FORMATn
, где n - это количество аргументов в утверждении. У нас их пока два: ожидаемые данные и данные для сравнения, которые передаются вторым и третьим аргументом макроса: exp
и data
. В качестве первого аргумента передается имя специальной функции сравнения AssertContainerEquals
, котороая может быть реализована какtemplate <typename Container>
testing::AssertionResult AssertContainerEquals(const char* exp_expr,
const char* imp_expr,
const Container &expected,
const Container &data) {
if (expected.size() != data.size()) {
return testing::AssertionFailure() << "payload and "<< exp_expr << " (" << expected.size() << " != " << data.size() << ") sizes are not equals";
}
auto act_it = data.begin();
auto exp_it = std::begin(expected);
for (size_t i = 0U; i < expected.size(); i++) {
if (*act_it != *exp_it) {
return testing::AssertionFailure() << "payload and "<< exp_expr << " element [" << i << "] (" << std::hex << *act_it << " != " << *exp_it << ") are not matches" << std::dec;
}
exp_it++;
act_it++;
}
return testing::AssertionSuccess();
}
Специальный макрос GTest рассчитывает, что первыми аргументами идут строковые описания данных, а затем и сами данные. То есть если мы напишем сравнение
EXPECT_CONTAINER_EQ(GetExpectedResult(), container1);
функция будет вызвана следующим образом:AssertContainerEquals("GetExpectedResult()", "container1", GetExpectedResult(), container1);
Утверждение должно вернуть результат типа
testing::AssertionResult
, положительный или отрицательный, который к тому же может быть дополнен пояснениями в стиле iostream
.Можно сделать смелое утверждение, что такой же макрос и функцию сравнения легко написать для массивов в си-стиле
#define EXPECT_RAW_ARRAY_EQ(exp, data, size) EXPECT_PRED_FORMAT3(AssertRawArrayEquals, (exp), (data), (size));
но проверять его мы, конечно же, не будем.
👍6
std::array & IAR
Чем может удивить нас IAR в реализации такого банального контейнера, как
Почему бы и нет, если это упростит множество функций?
Например,
То есть можно особо не церемониться и даже не пытаться вернуть какое-то значение. Все равно ничем хорошим вызов функции с атрибутами NORETURN и CAN_THROW не сможет закончиться.
Наша многострадальная функция
Но в специальном случае добавляется немного магии макросов:
Макрос
Интересно, что при
То есть была мысль сделать красиво через функцию
Чем может удивить нас IAR в реализации такого банального контейнера, как
std::array
? Ничем. Возможно, в головы разработчиков одновременно пришли одинаковые идеи, или же реализацией стандартной библиотеки занимаются исключительно гениальные художники, но и тут тоже имеется специальный случай массива нулевой длины.Почему бы и нет, если это упростит множество функций?
Например,
at
:reference at(size_type) { // subscript mutable sequence with checking
__iar_Raise_ran();
}
То есть можно особо не церемониться и даже не пытаться вернуть какое-то значение. Все равно ничем хорошим вызов функции с атрибутами NORETURN и CAN_THROW не сможет закончиться.
Наша многострадальная функция
front
получила следующую реализацию в обычном состоянии.reference front() { // return first element of mutable sequence
return _Elems[0];
}
Но в специальном случае добавляется немного магии макросов:
reference front() { // return first element of mutable sequence
#if _ITERATOR_DEBUG_LEVEL == 2
_DEBUG_ERROR("array<T, 0>::front() invalid");
#elif _ITERATOR_DEBUG_LEVEL == 1
_SCL_SECURE_VALIDATE_RANGE(false);
#endif /* _ITERATOR_DEBUG_LEVEL */
return _Elems[0];
}
Макрос
_ITERATOR_DEBUG_LEVEL
частенько упоминается в реализации стандартной библиотеки по версии IAR, ведь он включает отладку итераторов. Для debug-сборки по умолчанию значение 2, для release - 0. Значение может быть задано пользователем.Интересно, что при
_ITERATOR_DEBUG_LEVEL
равном 2, макрос _DEBUG_ERROR
определяется как #define _DEBUG_ERROR(mesg) \
_DEBUG_ERROR2(mesg, __FILE__, __LINE__)
#define _DEBUG_ERROR2(mesg, file, line) \
assert(0)
//#define _DEBUG_ERROR2(mesg, file, line) \
//_Debug_message(mesg, file, line)
То есть была мысль сделать красиво через функцию
_Debug_message
, где в параметрах фигурирует и сообщение, и место возникновения ошибки. Однако что-то пошло не так, и эти строчки закомментированы, а все скатилось все к банальному assert
.👍5
Trivial default constructor
Тривиальный конструктор по умолчанию. Он же банальный конструктор. Отличается умом и сообразительностью и выдающимся нежеланием что-либо делать.
Документы говорят, что конструктор по умолчанию для класса
🔸 конструктор не объявлен пользователем (определен неявно или помечен как default при первом объявлении);
🔸 тип не имеет виртуальных функций;
🔸 нет виртуального наследования;
🔸 нет нестатических членов класса и инициализацией по умолчанию (после с++11);
🔸 базовые классы имеют тривиальный конструктор;
🔸 каждый нестатический член класса имеет тривиальный конструктор.
Любой тип данных совместимый с языком С (POD типы) можно тривиально сконструировать по умолчанию.
Есть даже специальная метафункция для проверки на причастность:
Ах да, скрыть конструктор в приватной секции тоже не получится.
Тривиальный конструктор по умолчанию. Он же банальный конструктор. Отличается умом и сообразительностью и выдающимся нежеланием что-либо делать.
Документы говорят, что конструктор по умолчанию для класса
T
будет тривиальным, если выполнены все условия из списка:🔸 конструктор не объявлен пользователем (определен неявно или помечен как default при первом объявлении);
🔸 тип не имеет виртуальных функций;
🔸 нет виртуального наследования;
🔸 нет нестатических членов класса и инициализацией по умолчанию (после с++11);
🔸 базовые классы имеют тривиальный конструктор;
🔸 каждый нестатический член класса имеет тривиальный конструктор.
Любой тип данных совместимый с языком С (POD типы) можно тривиально сконструировать по умолчанию.
Есть даже специальная метафункция для проверки на причастность:
std::is_trivially_default_constructible<T>Самый простой пример класса с банальным конструктором
class Simple {Проверка пройдена. Можно явно обозначить дефолтный конструктор, это ни на что не повлияет.
};
static_assert(std::is_trivially_default_constructible<Simple>::value);
Simple() = default;Только зачем нам такой конструктор, который ничего не делает? Видали мы таких: сидит, рамочки рисует, про ГОСТ что-то бормочет, а ты за него плату разводи... может быть удалить его? Теоретически это должно запретить пользователю создавать экземпляры класса.
Simple() = delete;Тут появляется два интересных следствия. Во-первых, формально наш класс перестал быть банальным.
prog.cc:25:68: error: static assertion failedВо-вторых, внезапно экземпляр класса все же можно создать!
static_assert(std::is_trivially_default_constructible<Simple>::value);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~
Simple s{}; // still compilable!Кажется, компилятор тоже отбрасывает всякие банальности раньше, чем мы успеваем их явно удалить. Причем GCC и IAR действуют одинаково до 17 стандарта включительно.
Ах да, скрыть конструктор в приватной секции тоже не получится.
class Simple {Этот код опять коварно скомпилируется. Но выход есть! A suivre.
private:
Simple() = default;
};
👍5😁2
Explicit trivial default constructor
Итак, у нас есть компиляторы, которые в грош не ставят тривиальный конструктор. Как же сделать их поведение более предсказуемым?
Нужно показать им мать Кузьмы. Либо вежливо попросить использовать конструктор только явно, то есть, добавить
Нельзя создать экземпляр класса:
Итак, у нас есть компиляторы, которые в грош не ставят тривиальный конструктор. Как же сделать их поведение более предсказуемым?
Нужно показать им мать Кузьмы. Либо вежливо попросить использовать конструктор только явно, то есть, добавить
explicit
.class Simple {Думаете, это бесполезно? Эффект есть. Во-первых, в GCC неким странным образом подавляется
public:
explicit Simple() = default;
};
warning: unused variable 's' [-Wunused-variable]
здесь:int main() {В IAR не сработает, но это и не главное. Перестанет работать копирующая инициализация:
Simple s{};
return 0;
}
Simple s = {};или такие вот неявные преобразования:
void f(Simple) {}Однако наш класс по-прежнему считается звенящей банальщиной.
f({});
static_assert(std::is_trivially_default_constructible<Simple>::value); // trueЭто хорошо, но собрались мы тут не ради этого. Даешь опасные эксперименты! Что если сделать конструктор приватным?
private:Этим мы навсегда вычеркнули класс из тривиального общества, но парадокс в том, что теперь он ведет себя более предсказуемо.
explicit Simple() = default;
Нельзя создать экземпляр класса:
error: 'constexpr Simple::Simple()' is private within this contextТеперь явно запретим конструктор.
explicit Simple() = delete;Эффект будет ровно такой же.
error: use of deleted function 'Simple::Simple()'Если же вам посчастливилось писать на с++20, то это все можно забыть как страшный сон. Обычный
Simple() = delete;
будет работать, как от него ожидаешь, и безо всяких пинков в виде explicit
. Жаль, что прогресс IAR до этого стандарта еще не дошел.👍7
Ecplicit & С++20
Раз уж речь зашла о ключевом слове
Казалось бы, что тут можно еще улучшить? Это же просто запрет использования неявных преобразований и инициализации копированием.
А что если охота берет использовать
Допустим, есть у нас класс
Как видите, это действительно уменьшает количество нетворческого и даже шаблонного кода.
Что значит никогда не пригодится? Вы псих!
Раз уж речь зашла о ключевом слове
explicit
, то в с++20 нас ждет интересное нововведение.Казалось бы, что тут можно еще улучшить? Это же просто запрет использования неявных преобразований и инициализации копированием.
А что если охота берет использовать
explicit
не для всех конструкторов?! Что значит зачем? Вы мещанин!Допустим, есть у нас класс
Digger
, который мы можем создать, подкладывая объекты разнообразных типов в конструктор.struct Digger {Если
template <class T>
Digger(T u) {}
}
T
- целое число, то никаких сложностей нет, но ведь можно злодейски подсунуть в конструктор число 1.5F
. Будет непонятно, сколько нам нужно землекопов: один или два. Легко видеть, что нужно больше строгости для определенных типов. На помощь придет старое доброе SFINAE. Громоздкая классика жанра, сделаем explicit
все, что не is_integral
.struct Digger {Видимо, было много запросов на новый синтаксический сахарок и его таки подвезли. Теперь спецификатор можно использовать с константным выражением. Если выражение оценивается как
template <class U, std::enable_if_t<std::is_integral_v<U>, bool> = true>
Digger(U u) {}
template <class U, std::enable_if_t<!std::is_integral_v<U>, bool> = true>
explicit Digger(U u) {}
};
true
, то explicit
сработает, а иначе, извините, куда деваться.struct Digger {
template <class T>
explicit (!std::is_integral<T>::value) Empty(T) {};
}
Как видите, это действительно уменьшает количество нетворческого и даже шаблонного кода.
Что значит никогда не пригодится? Вы псих!
👍6😁1
Деструктор для int
Товарищи...
Товарищи, товарищи, вас всех интересует вопрос, если ли деструктор у примитивных типов данных. Прошу всех взглянуть на код.
Частичная специализация
Проверим, работает ли:
Разгадка, как всегда, сокрыта в стандарте. Глава 11.4.7 [class.dtor] стих 19:
Товарищи...
Товарищи, товарищи, вас всех интересует вопрос, если ли деструктор у примитивных типов данных. Прошу всех взглянуть на код.
struct Foo {Здесь деструктор кажется обычным затрапезным методом. Но стоит только нам взять простейшую метафункцию для определения наличия метода...
~Foo() {}
};
template <class, class = void>... и посмотреть вооруженным взглядом, как мы уже видим, что
struct HasMethod : std::false_type { };
template <class T>
struct HasMethod<T, std::void_t<decltype(&T::wanted_method)>> : std::true_type {};
wanted_method
не может быть деструктором, это не работает так, как мы ожидаем!Частичная специализация
HasMethod
покрывает случаи, когда у типа есть wanted_method
и возможно взять его адрес. Нет проблем, если метод не перегружен. Только вот брать адрес деструктора нельзя, компилятор по рукам надает! error: taking address of destructor 'Foo::~Foo()'Заменим выражение внутри
decltype
(и название метафункции на HasDestructor
)decltype(std::declval<T>().~T())Теперь мы понарошку создаем экземпляр типа
T
и попытаемся явно вызвать его деструктор.Проверим, работает ли:
struct Foo {Порядок, но что если мистер Стетем скажет: "Я запрещаю вам вызывать деструктор"?
};
static_assert(HasDestructor<Foo>::value); // OK
struct Foo {Неплохо, но ведь мы можем и
~Foo() = delete;
};
static_assert(HasDestructor<Foo>::value); // static assertion failed
int
какой-нибудь так проверить. static_assert(HasDestructor<int>::value); // OKТак-так, деструктор-то для int вызвать можно. Кто не верит, попробуйте
int i {2023};Да, вызов
i.~int32_t();
~int()
вызовет недоумение у компилятора, но ~int32_t()
вполне себе легален.Разгадка, как всегда, сокрыта в стандарте. Глава 11.4.7 [class.dtor] стих 19:
Примечание 10. Явный вызов деструктора может использоваться для любого имени скалярного типа ([expr.prim.id.dtor]). Это позволяет писать код, не зная, существует ли деструктор для конкретного типа.Например:
typedef int I;Псевдонима вполне хватило, чтоб все было шито-крыто. Что происходит с переменной, это уже другой вопрос. Это науке неизвестно. Наука еще не в курсе дела. Асса!
I* p;
p->I::~I();
👍8😁1
Aggregate types
Невероятные метаморфозы произошли с понятием агрегированного типа в последнее десятилетие. Хорошо только массиву, его эти пертурбации не коснулись.
Издревле агрегатами считались типы, у которых:
✅ нет объявленных пользователем конструкторов,
✅ нет приватных или защищенных нестатических членов,
✅ нет базовых классов,
✅ нет виртуальных функций.
С появлением с++11 разработка потеряла наивную простоту и обрела если не новые краски, то уж оттенков 50 одного цвета точно. Теперь у агрегатов не должно быть предоставленных пользователем, унаследованных или explicit конструкторов. Да, и раз уж появилась возможность инициализировать элементы по-умолчанию, то стоит прикрыть эту возможность для агрегированных типов.
Как вы знаете, не каждый конструктор будет считаться предоставленным пользователем. Например:
Новый с++14 принес не так много страдания. Однако в упорной борьбе агрегаты отстояли свое право инициализировать элементы по умолчанию.
Невероятные метаморфозы произошли с понятием агрегированного типа в последнее десятилетие. Хорошо только массиву, его эти пертурбации не коснулись.
Издревле агрегатами считались типы, у которых:
✅ нет объявленных пользователем конструкторов,
✅ нет приватных или защищенных нестатических членов,
✅ нет базовых классов,
✅ нет виртуальных функций.
struct Foo {Ссылки и методы допустимы, зато никаких конструкторов!
int x;
int &y;
int sum() const { return x + y; }
};
С появлением с++11 разработка потеряла наивную простоту и обрела если не новые краски, то уж оттенков 50 одного цвета точно. Теперь у агрегатов не должно быть предоставленных пользователем, унаследованных или explicit конструкторов. Да, и раз уж появилась возможность инициализировать элементы по-умолчанию, то стоит прикрыть эту возможность для агрегированных типов.
Как вы знаете, не каждый конструктор будет считаться предоставленным пользователем. Например:
struct Foo {Здесь конструктор не считается таковым. Тут пользователь взвалил всю работу на компилятор. Мол, делай что хочешь, но конструктор мне запили. Собственно, обсуждаемый ранее удаленный конструктор
Foo() = default;
};
Foo() = delete;
из этой же когорты, никак агрегату жить не мешает.struct Foo {Здесь же в объявлении структуры непонятно, какой будет конструктор, поэтому на всякий случай считаем его предоставленным пользователем. Логично, окончательное определение может быть вообще в другом файле.
Foo();
};
Foo::Foo() = default;
Новый с++14 принес не так много страдания. Однако в упорной борьбе агрегаты отстояли свое право инициализировать элементы по умолчанию.
struct Foo {Шли годы, появился очередной стандарт с++17, в котором появилось новые послабления. Господа стандартизаторы махнули рукой на всякие приличия и разрешили агрегатам наследоваться. Главное, чтоб они не делали это где-то в темной подворотне, все должно быть публично и не виртуально.
int x{42}; // aggregate!
};
class A {В реакционное время с++20 снова запрещены любые объявленные пользователем конструкторы. Унаследованные конструкторы тоже запрещены. Просто напомню, как выглядят унаследованные конструкторы:
public:
A(int i) :a(i) {}
private:
int a;
};
struct Foo : public A {
int x;
int y;
A a;
};
struct Foo : public A {Не только структура может быть агрегатом, класс тоже, разницы почти нет. Подобный тип определяется через способность к интуитивно понятной агрегатной инициализации:
using A::A; // non-aggregate!
int x;
int y;
};
T object={arg1,arg2,...},
а не совместимостью с Си.👍6
Бесполезный деструктор для int
Продолжая тему бесполезности деструкторов для элементарных типов, можно ради шутки задаться вопросом: что он должен делать? Самый серьезный ответ: ничего. В самом деле, чего вы хотите добиться, какой ресурс освободить? Оставьте, что плохого вам сделали переменные целочисленного типа?
Ну, допустим у меня велосипеда нет, хочу их немедленно в поликлинику сдать для опытов:
Наш любимый компилятор вдруг возвращает
Так-то оно так, да только трошечки не так.
Если присмотреться, то GCC вас честно предупреждает, что желаете вы странного.
Стоит добавить одну единственную функцию, и морок спадает
Стоит проверить, как поведет себя GCC, если слегка замаскировать вызов деструктора такой функцией:
Продолжая тему бесполезности деструкторов для элементарных типов, можно ради шутки задаться вопросом: что он должен делать? Самый серьезный ответ: ничего. В самом деле, чего вы хотите добиться, какой ресурс освободить? Оставьте, что плохого вам сделали переменные целочисленного типа?
Ну, допустим у меня велосипеда нет, хочу их немедленно в поликлинику сдать для опытов:
int main() {Любой нормальный компилятор покрутил бы у седеющего виска пальцем, вернул бы единицу и побежал бы дальше, но не таков GCC!
int i {1};
i.~int32_t();
return i;
}
Наш любимый компилятор вдруг возвращает
0
. "Какой молодец! - можете подумать вы, - он что, и память за меня обнулять будет в деструкторе?"Так-то оно так, да только трошечки не так.
Если присмотреться, то GCC вас честно предупреждает, что желаете вы странного.
warning: 'i' is used uninitialized [-Wuninitialized]Он так видит, вы пытаетесь вернуть неинициализированную переменную. Явный вызов деструктора как бы обнуляет инициализацию единицей. Рассмотрим ассемблерный код этого фрагмента
sub sp, sp, #12Вот мы резервируем немного памяти, сохраняем адрес стека в регистре
add r7, sp, #0
movs r3, #1
str r3, [r7, #4]
ldr r3, [r7]
mov r0, r3
R7
. Через регистр R3
сохраняем значение 1
по адресу в R7
(а это SP
) со сдвигом на 4 байта. Вот только значение, которое мы вернем, извлечем по адресу начала стека без всяких сдвигов. То есть попросту функция выбросит наружу какой-то кусок памяти в стеке. Не обязательно ноль, может и случайное число.Стоит добавить одну единственную функцию, и морок спадает
void fix(int *) {И вот уже функция ловко выбрасывает "правильный" кусок памяти.
}
...
int i {1};
fix(&i);
i.~int32_t();
bl fix(int*)Впрочем, это же неопределенное поведение. Радуйтесь, что контроллер не развалился на части.
ldr r3, [r7, #4]
mov r0, r3
Стоит проверить, как поведет себя GCC, если слегка замаскировать вызов деструктора такой функцией:
template <class T>В результате
void delete_it(T &t) {
t.~T();
}
warning
исчезает, функция delete_it
ничего не делает, и компилятор не опозорился. Вот и ладно, а то если долго смущать компилятор странными конструкциями, то рано или поздно он отплатит добрым багом.👍8
class vs struct
Если вдруг дознаватель на собеседовании светит вам лампой в лицо и требует объяснить разницу между структурой и классом, то нужно громко и четко сказать, что по умолчанию структура и класс имеют разный доступ к элементам. У структуры доступ открытый, у класса закрытый. Однако если ваш персональный Торквемада сверлит исподлобья самым тяжелым взглядом на который способен, добавьте, что у структуры и наследование по умолчанию открытое, а у класса - закрытое.
Ваш визави еще ждет чего-то? Что ж, остается только вздохнуть и напомнить древнюю орнитологическую мудрость: если нечто выглядит, как класс, плавает, как класс и крякает, как класс, то это, вероятно, и есть класс.
Нет никаких структур, все есть класс. Любую структуру вроде
Взять хоть механизм предварительного объявления:
Или вот конкретизированный спецификатор типа. Все, наверное, попадали в неловкую ситуацию, когда имя класса совпадает с именем локальной переменной в этой области видимости.
Вот еще пример. Вряд ли ошибусь, если предположу, что каждый уже попробовал модные перечисления с ограниченной областью видимости. Традиционно записывается через
Единственное место, где ключевое слово
Если вдруг дознаватель на собеседовании светит вам лампой в лицо и требует объяснить разницу между структурой и классом, то нужно громко и четко сказать, что по умолчанию структура и класс имеют разный доступ к элементам. У структуры доступ открытый, у класса закрытый. Однако если ваш персональный Торквемада сверлит исподлобья самым тяжелым взглядом на который способен, добавьте, что у структуры и наследование по умолчанию открытое, а у класса - закрытое.
Ваш визави еще ждет чего-то? Что ж, остается только вздохнуть и напомнить древнюю орнитологическую мудрость: если нечто выглядит, как класс, плавает, как класс и крякает, как класс, то это, вероятно, и есть класс.
Нет никаких структур, все есть класс. Любую структуру вроде
struct Foo {...}
вполне можно заменить на class Foo {public: ... }
без ущерба для здоровья. Да язык и сам не особо разбирает кто есть кто! Взять хоть механизм предварительного объявления:
struct Foo;После этого смелого заявления можно описать
Foo
как класс:class Foo {};и это не вызовет особых вопросов. Может, компилятор пожурит немного за неразборчивость.
Или вот конкретизированный спецификатор типа. Все, наверное, попадали в неловкую ситуацию, когда имя класса совпадает с именем локальной переменной в этой области видимости.
struct Olga {};Да, компилятор косо на вас посмотрит и скажет что-то вроде:
int func {
int Olga;
Olga e{};
}
error: expected ';' before 'e'
Если уж имя переменной вам слаще самого старого вина, то остается только конкретизировать тип Olga
:struct Olga e{};хоть нужна именно структура
Olga
, но и class Olga e{};
прекрасно работает.Вот еще пример. Вряд ли ошибусь, если предположу, что каждый уже попробовал модные перечисления с ограниченной областью видимости. Традиционно записывается через
enum class
:enum class MyBool : int32_t {Забавно, что эквивалентной записью будет
kBullshit = 0,
kDeadAss = 1
};
enum struct
:enum struct MyBool : int32_t { ... };Не стоит искать правду и у относительно новой метафункции
is_class_v
:struct S {};она не выявит никаких различий.
class C {};
static_assert(std::is_class_v<S>); // OK
static_assert(std::is_class_v<C>); // OK
Единственное место, где ключевое слово
class
нельзя заменить struct
, так это в шаблонахtemplate <class R>Вернее, заменить можно, но только в
struct Bar {};
c++20
, если R
- это реальный тип.struct R {};Хотя это уже сильно притянуто за уши.
template <struct R>
struct Bar{};
struct
тут в другом смысле употребляется, его можно вообще опустить или заменить на class
.template <class R>Ваш собеседник все еще не просиял, а наоборот побагровел от душноты, и глаз его дергается? Время удирать. Вдогонку вы еще можете расслышать что-то про совместимость структур с Си, но не слушайте речи еретиков.
struct is_R : std::false_type {};
template <>
struct is_R <class R> : std::true_type {};
👍15😁1
POD. Пыльная быль
Plain Old Data или старые добрые данные, если по-нашему. Старые, потому как нужно уязвить старперов-сишников, которые еще копошатся в своем древнем языке, но вот-вот будут уничтожены стильными и модными, как свинговый джаз, плюсовиками. Добрые оттого, что эти данные простые, понятные и предсказуемые: всегда очевидно, как объект разляжется в памяти. Понятие POD старше даже iPod, это не какое-то изобретение неуемных прогрессистов-стандартизаторов, а связующее звено между обезьяной и человеком... то есть между С и С++.
Уже в стандарте с++98 этот термин активно используется. В документе
Просто напомню, что указатель на элемент класса декларируется вот так:
если у
POD-структура есть агрегат. Как мы хорошо знаем, агрегатами в те далекие и мрачные времена, согласно
Если конструктор помешает структуре быть POD, то функция в составе структуры не препятствует. Статические члены классов вообще попросту игнорируются. Тупо откопипастить подобный тип в Си не получится, только перенести на подобную структуру с аналогичной компоновкой элементов.
Интересно, что указатель на элемент структуры (да, структура в составе может иметь член этой самой структуры), согласно определению, лишает тип звания POD. Хотя этот же стандарт определяет указатель на элемент класса как скалярный тип, который абсолютно POD! Хорошенькое дельце, мы включаем POD элемент в состав структуры, и она моментально этого же звания лишается. Налицо серьезное противоречие, описанное в
Не теряйте бдительности, на этом проблемы не закончились. A suivre.
Plain Old Data или старые добрые данные, если по-нашему. Старые, потому как нужно уязвить старперов-сишников, которые еще копошатся в своем древнем языке, но вот-вот будут уничтожены стильными и модными, как свинговый джаз, плюсовиками. Добрые оттого, что эти данные простые, понятные и предсказуемые: всегда очевидно, как объект разляжется в памяти. Понятие POD старше даже iPod, это не какое-то изобретение неуемных прогрессистов-стандартизаторов, а связующее звено между обезьяной и человеком... то есть между С и С++.
Уже в стандарте с++98 этот термин активно используется. В документе
ISO/IEC 14882:1998
в разделе 3.9 Types
стих 10 можно найти определение что есть POD тип:10 Арифметические типы, типы перечислений, указатели и указатели на элементы класса, а также cv-квалифицированные версии этих типов называют скалярными типами. Скалярные типы, POD-структуры, POD-объединения или массивы таких типов, а также cv-квалифицированные версии типов называют POD типами.Ну теперь-то все и прояснилось, кроме определения непосредственно POD-структур и POD-объединений. Листаем дальше до главы
9 Classes [class]
, чихаем от поднявшейся пыли и читаем:POD-структура - это агрегатный класс, который не имеет определенных пользователем оператора присваивания копированием и деструктора и не содержит нестатических членов: указателей на элемент класса, не POD-структур или не POD-объединений (или массивов подобных типов) или ссылок.POD-объединения определяются подобным же образом, замнем для ясности.
Просто напомню, что указатель на элемент класса декларируется вот так:
int Bar::* pointer;
pointer
объявляется указателем на нестатический член класса Bar
типа int
;если у
Bar
есть такой элемент, то можно исполнить что-то вроде:struct Bar {Запретили его в POD включать понятно почему, в Си таким развратом заниматься не дозволено, и нечего народ баламутить.
int count;
};
Bar b;
int Bar:: *pointer;
pointer = &Bar::count;
b.*pointer = 100;
POD-структура есть агрегат. Как мы хорошо знаем, агрегатами в те далекие и мрачные времена, согласно
8.5.1 Aggregates [dcl.init.aggr]
:1 Агрегат - это массив или класс без определенного пользователем конструктора, без закрытых или защищенных нестатических элементов, без базового класса и без виртуальных функций.Все вроде бы логично, POD - это такие простецкие типы, которые можно перенести на подобные структуры на языке Си. Дьявол опять оказался в деталях.
Если конструктор помешает структуре быть POD, то функция в составе структуры не препятствует. Статические члены классов вообще попросту игнорируются. Тупо откопипастить подобный тип в Си не получится, только перенести на подобную структуру с аналогичной компоновкой элементов.
struct Foo {
int x;
Foo() {} // non-POD
int Foo:: *ptr; // non-POD! wat?!
void doSmth() {} // OK
static int z; // OK
};
Интересно, что указатель на элемент структуры (да, структура в составе может иметь член этой самой структуры), согласно определению, лишает тип звания POD. Хотя этот же стандарт определяет указатель на элемент класса как скалярный тип, который абсолютно POD! Хорошенькое дельце, мы включаем POD элемент в состав структуры, и она моментально этого же звания лишается. Налицо серьезное противоречие, описанное в
CWG 148 (POD classes and pointers to members)
. После дебатов и голосования, чуть не перешедшего в грязную потасовку, было решено ограничение на указатель убрать. В следующей редакции стандарта (с++03) уже изъяли фразу про указатели на элемент класса. Что вы с ним будете делать в других языках, это ваши проблемы, как и ваши ожидания.Не теряйте бдительности, на этом проблемы не закончились. A suivre.
👍6🤔1
Срочно в номер! std::is_constructible & IAR.
Как только начинаешь думать, что и IAR - нормальный компилятор, так он тут же спешит опровергнуть твое нелепое заблуждение. Допустим, появилась новая версия некой абстрактной библиотеки, чьи авторы захотели стать стильными и модными, и метафункций там появилось, как у дурака фантиков. Тут же тащим ее в проект и... ломается сборка! В GCC все проходит безболезненно, а IAR не компилирует. В мозг мерзким аспидом заползает нехорошее подозрение, что не учтен какой-нибудь очередной вздорный каприз IAR (версии
Процесс охоты на неведомый баг настолько увлекательный, что забываешь поесть, поспать и даже как дышать и правильно стучать сердцем. В какой-то момент можно услышать голоса в голове, которые настоятельно советуют проверить
Нет нужды объяснять, что делает
Можно ли создать объект функции стандартной библиотеки с аргументом типа int? Отвечает GCC:
А что IAR?
- Можно, только отравишься"
Видимо, IAR просто не любит
Как только начинаешь думать, что и IAR - нормальный компилятор, так он тут же спешит опровергнуть твое нелепое заблуждение. Допустим, появилась новая версия некой абстрактной библиотеки, чьи авторы захотели стать стильными и модными, и метафункций там появилось, как у дурака фантиков. Тут же тащим ее в проект и... ломается сборка! В GCC все проходит безболезненно, а IAR не компилирует. В мозг мерзким аспидом заползает нехорошее подозрение, что не учтен какой-нибудь очередной вздорный каприз IAR (версии
8.50.х
, если спросят).Процесс охоты на неведомый баг настолько увлекательный, что забываешь поесть, поспать и даже как дышать и правильно стучать сердцем. В какой-то момент можно услышать голоса в голове, которые настоятельно советуют проверить
std::is_constructible
. "Да что они понимают", - подумаешь с раздражением, но проверить придется на всякий случай.Нет нужды объяснять, что делает
is_constructible
, но я объясню.template< class T, class... Args >В двух словах, проверяет, можно ли создать объект типа
struct is_constructible;
T
с набором аргументов Args
. Либо допустимо ли вызвать такой конструктор T obj(std::declval<Args>()...);
Для простых типов, не сложнее апельсина, все работает прекрасно. Чудеса начинаются там, где в ход идут изощренные типы вроде std::function
.Можно ли создать объект функции стандартной библиотеки с аргументом типа int? Отвечает GCC:
static_assert (std::is_constructible<std::function<void()>, int>::value); // falseИ это правильный ответ! При попытке создать такой функциональный объект вы получите кучу негодования от GCC
std::function<void()> f_ {1};Вы там что, единицу вызывать как функцию собрались? Совсем с ума посходили.
In function 'int main()':
error: no matching function for call to 'std::function<void()>::function(<brace-enclosed initializer list>)'
А что IAR?
static_assert (std::is_constructible<std::function<void()>, int>::value); // trueКак? Компилятор настолько хорош, что и из констант может делать вызываемые объекты? Нет, только попробуйте и тут же отхватите от IAR порцию оскорблений.
std::invoke(std::forward<_Valtys>(_Vals)...);При разборе полетов мы неизбежно упремся в вызов внутренней функции
Error[Pe304]: no instance of function template "std::invoke" matches the argument list
argument types are: (__int32_t)
__is_constructible
. Она ожидаемо дает ложноположительный результат для нашего интересного типа.static_assert (__is_constructible(std::function<void()>, int)); // true"- А эти ягоды можно есть?
- Можно, только отравишься"
Видимо, IAR просто не любит
std::function
и хочет, чтоб все, кто эту гадость использует в своих проектах, колоссально страдали. Не зря мы избавились от этой ереси в свое время.👍11