Обновить
2

Пользователь

Отправить сообщение

я прошу прощения за static inline в декларации переменной tok_received: профессиональная деформация, мне недавно пришлось писа́ть код, который одновременно компилируется в C и C++, а в этом случае static inline единственный спокойный вариант (хотя это сочетание для плюсов бессмысленно и редуцируется просто до static).

Но что интересно: если static убрать (а лучше так и сделать, чтобы не плодить инстансы переменной), то ошибка линкера возникает и без volatile! То есть компилятор не решается убрать переменную с external linkage, а линкер (возможно, прежде, чем ему придет идея убрать) отругивается на то, что её нечем инициализировать. Но это всё слишком тонко, поэтому надежнее всё же volatile оставить, даже в варианте без static (с одним inline).

ну ей так и положено по стандарту :)

Интересно, что ваше решение можно упростить почти до нуля (https://godbolt.org/z/GnbxYvKq5)

#include <iostream>

// user-defined
extern void init_token();

inline bool token_initialized = (init_token(), true);

// если не раскомментировать, то будет ошибка линкера
//void init_token() { std::cout << "init" << std::endl; }

int main() {

}

Судя по всему, компилятор не имеет права убрать инициализацию, имеющую явный побочный эффект (ибо справедливо считает, что меняется observable behavior программы).

Весьма вероятно, что volatile - ваш друг!

Компилятор не имеет права убрать присвоение volatile-переменной.

Я немного упростил код, но, надеюсь, не повредил существо дела:

Файл a.hpp:

#include <iostream>

extern bool tok;
//static inline const bool tok_received = tok;
static inline const volatile bool tok_received = tok;

inline void f(){
  std::cout << "\nHello!\n";
}

Файл main.cpp:

#include <iostream>
#include "a.hpp"

bool initTok(){
  std::cout << "\ntoken initialized!\n";
  return true;
}

// bool tok = initTok();

int main(){
  f();
}

g++ -O3 main.cpp - ошибка линкера (g++ 13.3.0, Ubuntu)

Если убрать volatile из объявления, ошибка линкера исчезает, инициализация не вызывается.

Если, наоборот, в main.cpp раскомментироваать определение флага tok, ошибка ичезает и инициализация вызывается.

Вероятно, вы правы. Хотя, я вернул бы end(). Но то, что end() будет больше, чем итератор, который указывал бы на запрашиваемый ключ если бы оный ключ был в коллекции... этот факт, пожалуй, да, нарушает стройность всей картины.

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

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

/.Т.е. у вас есть объект у него есть одно особое поле - ключ. Т.е. вам тут лучше уже подходит std::map, а не std::set. В качестве ключа используйте ваш ключ, в качестве данных - все остальные данные. Это поле вообще можно отдельно от данных держать.

Инода нужен доступ из методов объекта «все остальные данные» к ключу, а этот объект в std::map не знает, под каким ключом он лежит. Или надо передать этот объект в к-л функцию, и опять же, если функции интересен также и ключ, передавать его отдельно? Это весьма неудобно. Собственно, для этих случаев std::set и предназначен, для остальных, я согласен, std::map — самый естественный выбор.

Ну а пожалуй сам по себе список не вредный, несмотря на... (то, о чем многие уже сказали).

С выводами можно соглашаться или не соглашаться.

С чем я бы не согласился:

макросы: здесь желаемое выдается за действительное. Пока нет рефлексии, никуда от них не деться. Когда рефлексия появится в C++26(?), посмотрим... так что хоронить макросы рановато.

floating point - ну про это действительно действительно бред. Если в программе нужны вещественные числа, то они нужны. Поделить ноль на ноль и обвинять язык в том, что получился NaN?

exceptions - тут я выскажу мое субъективное (но не только мое) мнение: это одно из самых масштабных заблуждений в программировании рубежа последних двух веков, этакий адский соблазн, на который очень многие поначалу повелись. Но оказалось, что исключения создают намного больше проблем, чем решают. Типизированная обработка исключений очень быстро наталкивается на несовместимость с любыми видами шаблонов-дженериков. Какое исключение должен бросать алгоритм сортировки? То же, что и предикат сравнения? А какое он бросает?... ну и т.д. Это не только в C++, в Джаве та же ситуация, причем там это очень выпукло нарисовалось. Возвращаясь к C++, все эти exception safety даром не даются. Вот из недавнего : std::move ничего не только не двигает, но и не дает подвинуть, потому что в move-конструкторе забыли написать noexcept. И сколько труда и говнокода из-за этого в стандартнных библиотеках! И весь этот огород из-за того, что кому-то лень написать if(something_failed) return ?

То, что в Rust отказались от эксепшенов, это совсем не случайное решение, скорее результат работы над прежними ошибками и заблуждениями (убедительная просьба не флеймить на тему C++ vs Rust, я вовсе не про то, какой язык хуже или лучше, скорее про то, какой позже и какой раньше).

Да, атомарный shared_ptr - это, наверное, теретически лучший паттерн для реализации атомарного сложного состояния, но в современных компиляторах (я проврял clang 21.1.0 gcc 15.2 ) он не lock free, к сожалению (по крайней мере для x86-64). Поэтому смысла в нем пока немного. Правда, если количество взаимодействющих нитей ограничено, возможны его сравнительно несложные самодельные lock-free паллиативы (с заранее заготовленным небольшим пулом объектов).

Информация

В рейтинге
4 482-й
Зарегистрирован
Активность