Chief Software Engineer in Game Development
Контакты
Местоположение
Россия, Санкт-Петербург и область, Санкт-Петербург

Достижения

Все достижения (16)

Наибольший вклад в теги

Все теги (87)

Лучшие ответы пользователя

Все ответы (194)
  • Как передать в лямбду контекст класса?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Drottarutarnum, p = { s }; - это Copy Initialization.
    В буквальном смысле, что тут происходит. Сперва ты создаешь temporary local через конструктор Protocol::Protocol(Serial &s). Следом, через operator =, который сводится к конструктору копирования/перемещения, ты делаешь Copy Construction объекта p. Делается это через конструктор копирования/перемещения по умолчанию, который просто копирует/перемещает поля используя их конструкторы копирования/перемещения.

    Лямбда создается внутри temporary local значения, т.к. именно для него вызывается соответствующий конструктор. Далее эта лямбда остается жить в объекте Serial, а сам temporary local уничтожается сразу же после конструирования p.

    После этого в замыкании лямбды остается dangling pointer на уже уничтоженный this.

    Одним из возможных решений данной проблемы может быть не p = { s };, а p{ s }; или, как ты уже описал, p = s;. Такой тип инициализации уже называется Value Initialization. Но по своей сути это только отсрочка проблемы. Тебе придется очень пристально следить за жизнью такого объекта, т.к. вероятность обрести все тот же висячий указатель в замыкании в следствии неявного копирования крайне высока.

    Другим из возможных решений такой проблемы может быть использование std::enable_shared_from_this[?], std::shared_ptr[?] для хранения протокола и std::weak_ptr[?] в замыкании твоей лямбды. Этот метод гарантированно решит твою проблему, но может привести к ряду других проблем с возможной утечкой ресурсов.

    В качестве еще одного способа - научить Serial отцеплять лямбду уничтожаемого Protocol и написать правильный конструктор копирования Protocol, соблюдая правило 3/5/0.

    В общем смысле, тебе стоит внимательнее смотреть за тем, какие операции ты выполняешь над объектами, сырой указатель на которые ты хочешь где-то сохранить.
    Ответ написан
    2 комментария
  • Каким образом operator>> попадает в глобальное прогстранство имён?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тебе стоит ознакомиться с понятием ADL.

    Because of argument-dependent lookup, non-member functions and non-member operators defined in the same namespace as a class are considered part of the public interface of that class (if they are found through ADL)

    Т.е. любая внешняя перегрузка оператора для некоторого типа, определенная в пространстве имен этого типа, является частью интерфейса этого типа и доступна через ADL.

    Также через ADL доступны и дружественные функции, определенные по месту объявления дружественности и не имеющие предшествующего объявления.

    В итоге, operator>> никакими магическими силами не переносится в глобальное пространство имен. Он просто находится по ADL, т.к. правильно реализован и является частью интерфейса соответствующего типа.
    Ответ написан
    Комментировать
  • Какое время жизни у переменной?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    auto out = in;
    Тип переменной `out` будет `std::forward_list<T>`. [Пояснение 1], [Пояснение 2]

    Поэтому `out ` будет локальной переменной и будет иметь локальное время жизни.
    Исключением может быть только temporary lifetime extension. Тогда время жизни переменной продлится.
    The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.
    Ответ написан
    5 комментариев
  • Что не даёт на C++ писать кроссплатформенные приложения?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Да, как бы, ничего не мешает писать один С++ код для множества платформ. Почти весь мой трудовой стаж связан именно с разработкой кроссплатформенных игр. Я работал с несколькими (самодельными и не очень) движками и имею свое собственное детище, прекрасно и однозначно собирающееся на 5 целевых платформ (Win, Mac, Linux, ios, Android), к которым без труда можно добавить и консоли, и новые платформы.

    Нет, вру, не без труда. Попотеть над слоем абстракции придется. Но попотеть придется только над ним, все остальное заведется само, т.к. изначально написано в стандарте C++, без расширений под конкретные компиляторы, и с применением ряда очень важных для кроссплатформенности подходов.

    Кроссплатформенность подразумевает решение ряда вопросов, которые и выливаются в слой абстракции над операционной системой. Эти вопросы, зачастую, решать никто не хочет. Несколько раз встречал такое сам и еще мне рассказывали о том, как тот или иной движок сперва был только под конкретную ###, а потом решили портировать на @@@. Оказалось, что компилятор, которым всегда и собирали движок, нашпигован расширениями языка, которые конечно же все пользовали на 100%, и при смене компилятора ни один файл исходников не остался без доброй сотни ошибок. Т.е. переписывать надо было ВСЁ.

    Mercury13 хорошо рассказал про Unicode пути к файлам. Drakonoved правильно подметил про разделители путей к файлам. Максим Гришин очень хорошо напомнил про порядок следования байт. Это все и есть часть этого ряда вопросов.
    У каждой платформы есть свой API, которого не будет на другой платформе. Но на другой платформе будет свой API, со своими именами и схожей функциональностью. И работу с API надо абстрагировать от универсального кода.
    Еще, на одной платформе у тебя может быть разомкнутый главный цикл обработки сообщений (Win), а на другой - замкнутый (Android). Надо подстраиваться. GUI везде разный, надо подстраиваться. Сама структура приложения на одной платформе может быть монолитной, а на другой - композиционной. Графические и звуковые API могут быть и кроссплатформенными, однако простоты использования это им не прибавляет. Инициализация все равно будет платформозависимой.
    На самом деле даже в рамках работы на одной платформе надо соблюдать ряд правил, чтобы иметь возможность из одного кода получать и 32-битное приложение, и 64-битное тоже. Об этом неплохо написано на сайте разработчиков PVS-Studio.

    И все это решается. От части - с помощью архитектурных приемов. Один из таких я уже показывал в другом своем ответе.
    И еще эти вопросы можно не решать.
    ДубльГИС, например, уже давно работает на базе Qt, что сильно упростило им кроссплатформенную жизнь. Qt решает ряд проблем кроссплатформенности.
    Ответ написан
    10 комментариев
  • Как раскрыть parameter pack для передачи типов в шаблонную функцию?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ошибка, которую видно в коде, это ошибка использования parameter pack.
    В коде у тебя видно раскрутку списка параметров, в ходе которой список неизбежно станет пустым и произойдет попытка инстанцировать вызов mapClasses<>(). И именно эта попытка приводит к ошибке.

    И эту ошибку можно убрать. Для этого у нас сейчас есть аж целых три способа.

    Способ первый - C++17 constexpr if.
    template< typename U, typename... args >
    void mapClasses()
    {
    	// ...
    	if constexpr( sizeof...( args ) > 0 ) // Все понятно и без слов.
    	{
    		mapClasses<args...>();
    	}
    }


    Способ второй - частная специализация класа/структуры.
    template< typename... args >
    struct Map;
    
    template< typename U >
    struct Map<U>
    {
    	static inline void mapClasses()
    	{
    		// ...
    	}
    };
    
    template< typename U, typename... args >
    struct Map<U, args...>
    {
    	static inline void mapClasses()
    	{
    		Map<U>::mapClasses();
    		Map<args...>::mapClasses();
    	}
    };


    Способ третий - самый коварррный - использование SFINAE в классическом его смысле.
    template< typename U >
    void mapClasses()
    {
    	// ...
    }
    
    // SFINAE тут (аргумент функции) выключит вывод шаблона при пустом списке полей.
    // В этом случае доступным остается только верхний экземпляр функции.
    template< typename U, typename... args >
    void mapClasses( char (*)[ sizeof...( args ) > 0 ] = 0 )
    {
    	mapClasses<U>();
    	mapClasses<args...>();
    }
    Ответ написан
    4 комментария