Pull to refresh

Comments 34

Я что-то не понимаю, наверное, но с учетом ограничений на использование break и continue то же самое делается без лямд и C++ конструкцией do {… } while(0);
Именно что не понимаю, почитал стандарт, разобрался.
Я что-то не понимаю
Да. Это решение позволит выполнить произвольный участок кода один раз независимо от того, сколько раз будет вызвана функция, в которой он содержится. Этакий синглтон для участка кода.
У нее же фатальный недостаток, да и статью про нее не напишешь
А что за фатальный недостаток? Почитал — вроде как раз самое то что нужно автору и потокобезопасно.
Значит прочитайте еще про «фатальный недостаток» (https://ru.wikipedia.org/wiki/Синдром_неприятия_чужой_разработки)
DO_ONCE
(
    std::map<int, int> m;
    ...
); // Ошибка компиляции.
Верно, не учёл этого. Побочный эффект наличия запятых внутри вызова макроса. Устраняется так — в описании ставим переменное количество аргументов (...) вместо одного именованного, а вместо { body } пишем: __VA_ARGS__. Таким образом будут сохранены все аргументы, а так же — запятые между ними.
UFO just landed and posted this here
UFO just landed and posted this here
Кажется люди уже безнадежно отравлены c++11/с++14.
Если вы имеете в виду, что я сейчас могу написать пятью строчками из std то, для чего раньше нужен был бы буст и неделя на отладку (а на самом деле я просто не брался это писать) — да, я безнадёжно отравлен.
А у меня наоборот… одни программисты в Visual Studio написали чуток кода c++14 и теперь весь кросс платформенный проект не собирается для девайса, где gcc 4.8.3. Вся компания на ушах стоит. Первые говорят, что нужно делать апдейт компилятора, а вторые говорят, что вся система собрана проверенным временем 4.8.3 и ничего менять не будут, откатывайте свои изменения назад.
А речь всего о десятке строк кода. Идет вторая неделя разбирательств, взаимных упреков, обид.
Если проект — не однодневка по принципу «зарелизил и забыл», то я однозначно за обновление компилятора.
С другой стороны, это право компании требовать, чтобы программеры писали код в рамках каких угодно ограничений. И право программистов уйти из такой уомпании и устроиться в другую. Не вижу повода для драмы. Я вот на С программировать ни за что не пойду, да и на С++03 уже вряд ли.
Речь на самом деле даже не о программировании.
Вот такой пример:
Есть сложная программа, которая состоит из многих модулей, которые писались разными людьми. Программа собирается через cmake/make.
Программа использует Qt5.6.
Ладно, проапгрейдили gcc. Все вроде бы счастливы и начинают использовать c++14. Ненадолго.
Апгрейдим Qt до 5.7 и… о чудо… перестало собираться, потому, что cmake обнаружив Qt5.7 отчего-то устанавливает дополнительную опцию для нашего проекта --std=gnu++11, а люди уже вовсю используют c++14. А с Qt5.6 такого не было.
Это только маленький эпизод. Когда программа сразу для многих платформ и разных устройств то неизбежно всплывают проблемы связанные со стандартами на компиляторы, окружение, совместимостью со старыми продуктами и т.д. интересно, что менеджеры такие «технические» вопросы не решают, а отдают на откуп программистам и тут уже кто во что горазд. Каждый же считает себя самым умным, хоть и работает над узким направлением и какой-то одной платформой.
Мне кажется разумным не спешить с внедрением самых передовых фич. Немного консерватизма не повредит.
Согласен насчёт самых передовых фич, но насколько разумно считать таковой С++14 в конце 2016 года?..
А вот Qt 5.6 — хороший выбор, это LTS-версия c поддержкой до 2019 года. Если проект большой и сложность обновления версии высокая — тогда, может быть, и не стоит пока обновлять. У меня вот проекты относительно небольшие, я просто обновляю и смотрю, нет ли новых багов. То, что вы описали — флаги, изменения в окружении и т. д. — обычные рабочие моменты.
Насколько разумно для Qt5.7 выставлять флаги --std=gnu++11 в конце 2016 года?
И да, эти рабочие моменты так выбивают из колеи, что сто раз задумаешься, а стоит ли новая фича этой головной боли? Когда делаешь изменение в проекте и с настороженностью ждешь оповещений от Teamcity, что в результате твоего фикса один из напрямую не связанных проектов не собрался.
Непонятно, почему gnu, а не std.
А поддержка С++14 включается добавлением в .pro-файл строки
CONFIG += c++14
UFO just landed and posted this here

Что-то вы не так далеко пошли. Если ваш вариант чууууточку исправить:


#define DO_ONCE(callable)  { static bool _do_once_ = (callable(), true); (void)_do_once_; }

то:


  1. появляется возможность передавать любой вызываемый объект: функцию, функтор, лямбду
  2. так как лямбда описывается теперь явно, то можно управлять тем, что будет захвачено
  3. появляется (с некоторыми ограничениями, о которых ниже) возможность применить для C++03/98

По поводу последнего пункта: без модели памяти C++11 этот вариант не является потокобезопасным. Правда несколькими дополнительными движениями и это лечится.


Ну и примеры вызовов: http://ideone.com/Dfr3zk

А чем не устраивает «классика»:
static bool do_once = true;
if( do_once )
{
do_once = false;
// code
}

При желании это, зачем-то, можно спрятать в макрос. Поддерживается даже в С.
UFO just landed and posted this here

Для пользования варианта с std::call_once в таком же виде придётся чуточку украсить и так же завернуть в макрос, что бы не было видно этого вспомогательного флага:


#define \
 DO_ONCE(callable) do { \
   static std::once_flag _do_once_ ;\
   std::call_once(_do_once_, callable); \
 } while (false)

или так:


#define \
 DO_ONCE(callable, ...) do { \
   static std::once_flag _do_once_ ;\
   std::call_once(_do_once_, callable, __VA_ARGS__); \
 } while (false)

Из плюсов варианта автора (в доработке для любого вызываемого объекта) — не нужно подключать дополнительно заголовочный файл :)

UFO just landed and posted this here
Понимаю, что все фломастеры разные, но для чего однократно выполнять некий код в многопоточном блоке? Т.е. этот код исполнится неизвестно когда и неизвестно кем, но один раз. Если это чисто для вызова внутри распараллеленного цикла, то абсолютно очевидно, что этот код никак не связан с контейнером и итерируемыми данными (или можно брать любой элемент контейнера), то этот однократный код с тем же успехом можно разместить перед циклом.
В общем такая потребность больше смахивает на ошибку проектирования.
Не соглашусь. Допустим, есть несколько worker-тредов, которые в случайный момент обращаются к такому коду, и в нем в однократном блоке производится некая инициализация. Другое дело, что лично я не вижу смысла использовать для этого макросы, тем более когда есть стандартное средство.
Типа странного синглтона получается нечто.
UFO just landed and posted this here
писал для себя както
template<int RA, int RB>
bool once()
{
	static bool once_val = true;
	if (once_val)
	{
		once_val = false;
		return true;
	}
	return once_val;
}

#define ONCE once<__COUNTER__, __COUNTER__>()


используется соответственно
if (ONCE)                          
{                                  
	....
}                                  

Хорошая читаемость — субъективный критерий. Если не нравится/не подходит стандартное решение и хочется эксплуатировать потокобезопасную инициализацию, то можно это реализовать гораздо проще на современном С++ и без привлечения препроцессора. Например:
#include <iostream>

template<typename T>
struct OnceRunner
{
    OnceRunner(T&& func)
    {
        func();
    }
};

template<typename T>
OnceRunner<T> const& RunOnce(T&& func)
{
    static OnceRunner<T> const runner(std::move(func));
    return runner;
};

void Foo()
{
    std::cout << "Foo call" << std::endl;
    RunOnce([]()
    {
        std::cout << "Run me once" << std::endl;
    });
}

int main(int argc, char* argv[])
{
    Foo();
    Foo();
    Foo();
    return 0;
}

Результат:
Foo call
Run me once
Foo call
Foo call

При таком подходе можно вообще не задумываться об ограничениях на количество/состав операторов в коде инициализатора или о переопределнии неявных локальных переменных.

Код некорректен.


  1. Неправильная работа со сквозными ссылками (move вместо forward);
  2. Лишняя rvalue-ссылка там, где достаточно ссылки на const rvalue (в конструкторе OnceRunner);
  3. Но самое главное, что это просто не работает, т.к. тип не задаёт однозначно функциональный объект, а значит, среди разных объектов одного и того же типа (например, std::function) вызван будет только первый.

Так что совет крайне вредный, и std::call_once по-прежнему никто не отменял.

Sign up to leave a comment.

Articles