S>Такой вопрос — какую версию языка используете? И насколько глубоко знаете фишки новых версий?
На Альте стоит gcc10.
На убунте 18.04 — gcc7.
Их и использую.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
S>>Такой вопрос — какую версию языка используете? И насколько глубоко знаете фишки новых версий? LVV>На Альте стоит gcc10. LVV>На убунте 18.04 — gcc7. LVV>Их и использую.
Это как бы версии компилятора, которые поддерживают разные версии C++...
S>>>Такой вопрос — какую версию языка используете? И насколько глубоко знаете фишки новых версий? LVV>>На Альте стоит gcc10. LVV>>На убунте 18.04 — gcc7. LVV>>Их и использую. VF>Это как бы версии компилятора, которые поддерживают разные версии C++...
gcc10 полностью поддерживает С++17
gcc7 — C++14
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
S>Что из этого используете и что просто понимаете: S>
S> ranges
S> coroutines
S> concepts
S> modules
Модули — понятно еще с Модулы-2.
Сопрограммы — тоже читал еще в СССР. Но тогда это была экзотика. Сейчас — вполне понятно.
Ренжи прекрасно показал Иван Чукич в книжке Функциональное программирование на С++.
Так что наименее понятны пока концепты.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
LVV>Модули — понятно еще с Модулы-2. LVV>Сопрограммы — тоже читал еще в СССР. Но тогда это была экзотика. Сейчас — вполне понятно. LVV>Ренжи прекрасно показал Иван Чукич в книжке Функциональное программирование на С++. LVV>Так что наименее понятны пока концепты.
Теоретическое понимание сопрограмм и даже активное их использование, например, в Python3, не даёт опыта для C++ — тут те же идеи реализованы заметно иначе.
N>Теоретическое понимание сопрограмм и даже активное их использование, например, в Python3, не даёт опыта для C++ — тут те же идеи реализованы заметно иначе.
Не буду спорить.
Но если ты понимаешь концепцию сопрограмм, то язык уже не так важен. N>С модулями, насколько я видел, то же самое.
Модули — они и в Африке модули.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Shmj, Вы писали:
S>Что из этого используете и что просто понимаете:
S>
S> ranges
S> coroutines
S> concepts
S> modules
Понимаю все из этого (что тут может быть непонятного?). В просфессиональной деятельности пока нет возможности использовать ничего из этого, потому что мы пока застряли на C++14 (по разным причинам, организационным, техническим и пр.). В первую очередь из этого списка не хватает концептов, эта нехватка по старинке частично возмещается SFINAE. Кроме этого есть еще достаточно большой перечень фишек, которые хотелось бы поскорее начать использовать: fold expressions, structured bindings, mandatory copy/move elision, class template argument deduction и др. ranges тоже хотелось бы, конечно, но не в первую очередь. Без корутин и модулей вообще можно обходиться, в принципе.
Здравствуйте, Shmj, Вы писали:
S>Такой вопрос — какую версию языка используете?
С++17
S>И насколько глубоко знаете фишки новых версий?
Язык настолько огромен, что даже старые фишки не все знаю, а те что знаю, иногда забываю. А про новые — как вообще можно ответить на этот вопрос?
Вот я недавно узнал, что можно использовать std::error_code для собственных ошибок. А раньше я про него вообще не знал и никак не использовал. Может я и сейчас чего-то такого не знаю, так что ответить на этот вопрос не представляется возможным.
Здравствуйте, Shmj, Вы писали:
S>какую версию языка используете?
C++03 с расширениями от MS (VS 2008). Совместимость с новыми SDK/WDK проверяю компилятором/линкером от VS 2019.
S>И насколько глубоко знаете фишки новых версий?
Более-менее знаком с тем, что введено в C++11, с более свежими возможностями знаком очень фрагментарно и поверхностно, ибо для моих продуктов там ничего интересного нет.
Здравствуйте, LaptevVV, Вы писали:
N>>Теоретическое понимание сопрограмм и даже активное их использование, например, в Python3, не даёт опыта для C++ — тут те же идеи реализованы заметно иначе. LVV>Не буду спорить. LVV>Но если ты понимаешь концепцию сопрограмм, то язык уже не так важен.
Как знать, как знать.
Например, то, что get_future() для стандартного promise исполняется только один раз, заметно влияет на стиль.
N>>С модулями, насколько я видел, то же самое. LVV>Модули — они и в Африке модули.
Замерил я как-то раз время компиляции LLVM на десктопе с i9-9900k 32Gb и на Macbook Air M1 16Gb.
И макбук оказался быстрее где-то на 30%, это был шок...
Но потом понял, что на маке компилировалось клангом, а на десктопе gcc.
Поменял на clang и десктоп сравнялся с макбуком. Но шок все равно остался — М1 это конечно монстр
В общем, использую clang 14, C++17. Плюс линкер использую lld, он в разы быстрее стандартного линкера.
Здравствуйте, Евгений Музыченко, Вы писали:
vsb>>Стараюсь использовать C89
ЕМ>По объективным основаниям, или просто дело вкуса?
Нахожу своеобразную прелесть в примитивизме. Но я на С пишу редко и мало, в основном всякие склейки с системными API, недавно писал проект — через tigress обфускация и в браузер через wasm, альтернатив C не нашёл, JS-обфускаторы ерундовые показались, а тут всё по-взрослому. Если бы я писал много кода, наверное писал бы на современном C++.
Здравствуйте, vsb, Вы писали:
vsb>Нахожу своеобразную прелесть в примитивизме. Но я на С пишу редко и мало
Так даже мелкие куски приятнее писать на C++ "в стиле C", нежели на чистом C. Если, конечно, нет задачи побыстрее сделать корявый грязный код. Оно по объему и скорости точно такое же (разве только может подтягивать более объемный CRT startup), но по мелочи удобнее, и чаще бьет по рукам за грязь.
Здравствуйте, Евгений Музыченко, Вы писали:
vsb>>Нахожу своеобразную прелесть в примитивизме. Но я на С пишу редко и мало
ЕМ>Так даже мелкие куски приятнее писать на C++ "в стиле C", нежели на чистом C.
Определённый стандарт даёт определённые формальные ограничения. Разрабатывать свой набор ограничений — можно, но проще использовать готовые.
Если что-то очень важно, то я это использую. К примеру макросы с переменным числом аргументов это важно. Я их использую. Я даже шаблоны (_Generic) в макросах использую (для отладочной печати). А объявлять переменные в начале функции или в середине — это не очень важно.
R>Кроме этого есть еще достаточно большой перечень фишек, которые хотелось бы поскорее начать использовать: fold expressions, structured bindings, mandatory copy/move elision
А можете привести пример кода, который вы могли бы у себя улучшить при поддержке mandatory copy/move elision? Подозреваю, что ручная оптимизация возвращаемой из функций памяти будет не нужна.
Здравствуйте, Skorodum, Вы писали:
S>А можете привести пример кода, который вы могли бы у себя улучшить при поддержке mandatory copy/move elision? Подозреваю, что ручная оптимизация возвращаемой из функций памяти будет не нужна.
Mandatory copy/move elision здорово облегчают написание и улучшают качество разного рода создателей "сложных" объектов — парсеров, загрузчиков, фабрик и пр. Давай условно будем называвать такую процедуру "загрузчик". "Сложные" объекты, это, в первую очередь, такие объекты, которые имеют сложные связи с внешним миром и эти связи хочется заложить прямо на этапе конструирования объекта, чтобы исключить даже саму возможность существования невалидных объектов, даже временно. Также для таких объектов очень часто хочется запретить операции копирования/перемещения, а также конструктор по умолчанию. Также к сложным объектам можно отнести большие объекты с дорогой и сложной операцией копирования. Благодаря возможности mandatory copy/move elision загрузчик имеет возможность подготовить все необходимые данные, возможно, предварительно что-то подгрузить из DB, поискать все необходимые связи в графе объектов и т.п. и, наконец, создать сам объект и вернуть его по значению — даже при запрещенных конструкторах копирования и перемещения. Что при этом очень важно — загрузчик не навязывает внешнему миру способ владения объеком — верхняя логика сама решает, будет ли она владеть объектом через какой-то смарт-поинтер и через какой именно, либо просто создаст автоматический объект на стеке (в юнит-тесте, например).
Здравствуйте, rg45, Вы писали:
R>Mandatory copy/move elision здорово облегчают написание и улучшают качество разного рода создателей "сложных" объектов — парсеров, загрузчиков, фабрик и пр.
S>..единственный способ создать объект ComplexObject это вызов createComplexObject и никак иначе? И при этом никакого копирования памяти?
Да, все верно.
Единственное, что хочется заметить, что совсем не обязательно закрывать прямо все конструкторы в классе. Вполне возможен вариант, что какой-то конструкор остается открытым и доступным, просто его использование может быть сопряжено с какими-то более-менее сложными сопуствующими операциями — вычитка дополнительных данных из базы, поиск связей с другими объектами и пр. Поэтому функция-загрузчик может предоставляться скорее в качестве помощника, берущего на себя все сопутствующие заботы, а не в качестве полицая, запрещающего прямое создание.
S>>..единственный способ создать объект ComplexObject это вызов createComplexObject и никак иначе? И при этом никакого копирования памяти?
R>Да, все верно.
R>Единственное, что хочется заметить, что совсем не обязательно закрывать прямо все конструкторы в классе. Вполне возможен вариант, что какой-то конструкор остается открытым и доступным, просто его использование может быть сопряжено с какими-то более-менее сложными сопуствующими операциями — вычитка дополнительных данных из базы, поиск связей с другими объектами и пр. Поэтому функция-загрузчик может предоставляться скорее в качестве помощника, берущего на себя все сопутствующие заботы, а не в качестве полицая, запрещающего прямое создание.
Чтоб приблизить пример к более практическому применению, можно вспомнить про сериализацию:
class MyDeserializer
{
public:
ComplexObject DeserializeComplexObject();
}
При этом подразумевается, что и сам класс MyDeserializer может в себе хранить какие-то данные, которые используются для создания объекта — сам поток данных, из которого идет вычитка, карты объектов графа для построения связей и пр.
Ну и понятное дело, все эти фабрики, загрузчики, десериализаторы могут быть шаблонными в той или иной мере, мы здесь об этом не вспоминаем просто, чтоб сконцентрироваться на главном вопросе.
Спасибо. А как происходит обработка ошибок в фабрике? Там же доступны только исключения и тогда чем это отличается от того же самого прямо в конструкторе класса?
S>ComplexObject createComplexObject()
{
// как сообщаем об ошибках здесь? Только исключения
...
return ComplexObject(0);
}
Здравствуйте, Skorodum, Вы писали:
S>Спасибо. А как происходит обработка ошибок в фабрике? Там же доступны только исключения и тогда чем это отличается от того же самого прямо в конструкторе класса? S>
S>>ComplexObject createComplexObject()
S>{
S> // как сообщаем об ошибках здесь? Только исключения
S> ...
S> return ComplexObject(0);
S>}
S>
Логически фабрику можно рассматривать как некое расширение конструктора. Соответственно и стратегия обработки ошибок такая же, как в конструкторах — исключения.
Здравствуйте, rg45, Вы писали:
R>Логически фабрику можно рассматривать как некое расширение конструктора. Соответственно и стратегия обработки ошибок такая же, как в конструкторах — исключения.
Наверное, еще std::optional можно использовать.
Здравствуйте, Skorodum, Вы писали:
R>>Логически фабрику можно рассматривать как некое расширение конструктора. Соответственно и стратегия обработки ошибок такая же, как в конструкторах — исключения. S>Наверное, еще std::optional можно использовать.
Не-не-не, это по факту будет равносильно возврату к старой схеме со смарт-поинтерами. Смотрите, какие возможности предоставляет фабрика с mandatory copy/move elision:
#include <iostream>
#include <string>
#include <vector>
#include <memory>
struct Foo
{
const int id;
explicit Foo(int id) : id(id) {}
Foo() = delete;
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
////////////////////////////////////////////////////////////////////////////////////////////////
Foo(Foo&&) = delete; // перемещение не обязательно запрещать явно
Foo& operator=(Foo&&) = delete; // оно автоматически запрещается, когда мы запрещаем копированиеvoid test() const { std::cout << "Foo: " << id << std::endl; }
};
Foo make_foo()
{
static int last_id {};
return Foo(++last_id);
}
int main()
{
auto foo = make_foo();
const auto uptr = std::unique_ptr<Foo>(new Foo(make_foo()));
const auto sptr = std::shared_ptr<Foo>(new Foo(make_foo()));
foo.test();
uptr->test();
sptr->test();
}
То есть, мы можем выбирать стратегию владения объектом за пределами фабрики — вот, что важно. Это может быть любой тип смарпоинтера, а может быть просто объект на стеке с автоматическим временем жизни. Если же изменить тип результата make_foo на std::optional<Foo>, все эти возможности тут же пропадут.
Здравствуйте, rg45, Вы писали:
R>Если же изменить тип результата make_foo на std::optional<Foo>, все эти возможности тут же пропадут.
Понял: мы же не может Foo в optional копировать. Спасибо.
Здравствуйте, Skorodum, Вы писали:
R>>Если же изменить тип результата make_foo на std::optional<Foo>, все эти возможности тут же пропадут. S>Понял: мы же не может Foo в optional копировать. Спасибо.
Да, именно. Если резюмировать, то mandatory copy/move elision можно рассматривать как возможность расширения конструкторов. Сам конструктор содержит в себе только те действия, которые свойственны непосредственно классу объекта. А над этим мы можем еще построить массу разнообразных фабрик/загрузчиков/парсеров и т.п., в которых будут выполняться действия по созданию объекта, специфичные для мира, в котором этот объект создается. И в итоге всегда будем иметь под рукой простую и удобную процедуру создания объекта.