Ответы пользователя по тегу C++
  • Какую литературу по С++ и базовым знаниям выбрать для начинающего?

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

    Alex J. Champandard - AI Game Development (aigamedev.com; github; ai cources)
    Дополнительную литературу смотри на том-же амазоне по релевантности с книгой Алекса.

    Aurélien Géron - Hands-On Machine Learning
    Ответ написан
    Комментировать
  • Как раскрыть 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 комментария
  • Как выделяется память в классах?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ответ на это есть в стандарте языка.
    Для начала, у нас есть спецификация размещения.
    Во вторых, у нас есть спецификация полей класса.
    10-й пункт спецификации полей класса говорит о том, что для полей класса недопустимы спецификаторы extern и thread_local, а значит допустимы только static и mutable.
    Спецификатор mutable не влияет на фактическое размещение поля класса.

    Отсюда выводится, что на размещение поля класса влияет только спецификатор static, который переносит поле в область постоянного размещения.

    В остальном, размещение всех нестатических полей класса производится внутри памяти создаваемого объекта.
    Ответ написан
    Комментировать
  • Описание класса в .cpp?

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

    Сам класс в .h, а его описание в .cpp.

    Чтобы тебя правильно понимали, сперва нужно понять общую терминологию, а далее - этой терминологией нужно корректно пользоваться.
    У нас есть два строгих термина: Declaration (объявление) и Definition (определение).
    Замечу одну важную вещь - для определения характерно ODR (правило однократного определения), но пока на это можно не отвлекаться, потом надо будет.

    А потом к основному main.cpp подключаю просто файл .h

    А для чего ты это делаешь? Какую цель ты преследуешь, когда сперва объявляешь свой тип в отдельном файле, а потом говоришь препроцессору включить этот файл в тело исходного кода?
    Один раз я уже отвечал на подобный вопрос, повторю только место, где начинается важная сейчас информация: "С точки зрения компилятора есть только один формат файла - формат исходного кода, который ему и надо обработать".

    Компилятору известно только понятие Translation Unit. Он ничего не понимает во всех этих .h/.inl/.inc/.c/.cpp/...
    Для правильной сборки кода ты должен рассказать компилятору о том, как из всей понятной только тебе бурды собрать вожделенный для него модуль трансляции, в котором будет весь исчерпывающий код для завершения компиляции.

    Тебе незачем лепить файлы только ради объявлений и определений. Все равно для компилятора это будет собрано в один файл. А такие системы сборки, как FastBuild или IncrediBuild и вовсе весть проект вгоняют в один файл и только потом кормят этим файлом компилятор.

    Получается какой-то костыль.

    И это ошибочный вывод. Вот теперь давай разбираться в ODR.
    One Definition Rule гарантирует нам детерминизм любого используемого в коде типа, любой функции, любой переменной в рамках одной области видимости (но не в разных).
    Это правило позволяет сказать, что свойства программы однозначно определены до процесса трансляции и не изменяются после него.

    Любой мелкий проект ты всегда можешь написать в одном файле на 2 килостроки. Это не страшно.
    Когда у тебя более-менее большой проект, один файл у тебя уже не получится.
    Появляется необходимость заниматься архитектурой кода: выводить сущности, связывать их, получать зависимости. Помимо архитектуры кода, появляется необходимость наладить грамотное разделение кода на файлы. А файлы нужно грамотно компоновать вместе. Появляется необходимость в макроархитектуре проекта.
    Тут на помощь и приходят устоявшиеся правила разделения кода по типам файлов.

    В таких условиях очень важно соблюдать ODR и крайне важно понимать смысл таких ключевых слов, как static и inline.
    А помимо ODR тут еще встает вопрос избыточной компиляции кода - когда ненужные объявления все равно включаются в исходный код и впустую тратят процессорное время на свою трансляцию. В этом случае становится важно не включать лишние объявления или ненужные определения в заголовочные файлы.

    И именно поэтому, если определение сущности можно выделить в отдельный модуль трансляции, то это лучше сделать. Если тип явным образом не используется в других модулях проекта, его лучше загнать в файл исходного кода. Если функция не предназначена для встраивания, ее лучше загнать в файл исходного кода. Если есть частная специализация шаблона, которая используется только в одном файле исходного кода, то лучше там ее и определить.
    В этом случае процесс сборки твоего большого проекта будет легким и быстрым.
    Ответ написан
    Комментировать
  • Как создается единая физическая-графическая модель?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    И почему крупные компании не могут реализовать свою такую технологию, а покупают ее у других?

    Ответ будет таков
    5a2b8fc21c685856187802.jpeg

    Если компания крута, она, как мыслящий организм, прекрасно поймет, на что время тратить разумно, а на что - нет.

    А почему так мало игр ее использует?

    Скажем прямо, с "еще парой игр" ты слегка промахнулся, ведь Morpheme и Euphoria от Natural Motion используется далеко не в паре просто игр. Полный список игр не раскрывается и не может быть раскрыт по простым причинам своей масштабности. Это очень распространенный и очень мощный инструмент.

    Теперь стал интерисовать вопрос - как прикрутить к этому всему физику.

    Это архитектурные вопросы. Решение очень сильно зависит от того, как у тебя построена математическая модель мира. Мат. модель производит связь в принципе всех компонентов мира между собой.
    Для снижения трудоемкости операций, сетку коллизии нередко делают более разреженной, нежели сетку графической модели. Бывает, что это никак не сказывается на внешнем виде модели. Бывает, что это сильно сказывается. По своей форме коллизионная модель очень часто и очень сильно отличается от графической.

    Нередко модели коллизии и физики представлены разными подсистемами и работают с разными данными для одного и того же графического объекта. Это сделано для того, чтобы физику считать на более простых моделях, а более деликатные моменты обсчитывать уже после подтверждения физического контакта и с применением более детальных данных об объекте.

    Тебе стоит представить сцену как набор параллельных пространств. Одно пространство ты видишь. Другое - можешь только потрогать. Третье пространство лишь ощущается как набор сил, действующих на тебя. Есть еще много таких пространств, некоторые из них и вовсе недоступны для нормального представления.
    Пространство анимаций никак не связано с графическим пространством, но косвенно влияет на деформации объекта в графическом пространстве. Все дело в том, что и пространств анимации может быть несколько. Физическое пространство может оказывать влияние на анимации, эту связь лучше выделить в отдельное пространство со своими правилами.
    Все пространства наделяют объект своими свойствами благодаря работе мат. модели мира.

    Более подробно можно узнать в соответствующих источниках. Нужна секция: "Game engine development".
    Часть этих книг есть в русском переводе.
    Ответ написан
    Комментировать
  • Как проверить на утечки памяти?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Больше трех лет пользуюсь инструментом Visual Leak Detector. Это тихий и неприхотливый инструмент, требующий соблюдения всего двух правил: вписать один include в одном cpp и собрать отладочную конфигурацию проекта.

    После завершения отлаживаемого процесса, VLD пишет в (файл/консоль/debug output) все найденные утечки со стеками выделения утекшей памяти. К отчету об утечке, помимо стека, еще добавляется HEX dump самого участка памяти, чтобы можно было на глаз сориентироваться, что за память утекла.
    А если утечек нет, VLD утешительно говорит что все нормально.

    Проект является открытым, доступен на github, стабильно развивается, выкладывается для современных версий Visual Studio и доступен из Extensions Manager самой студии.

    Больше всего эта штука помогает экономить нервы. Как и регулярный статический анализ, постоянный мониторинг утечек памяти позволяет в более спокойном режиме вводить серьезные изменения в базовых уровнях проекта.

    Ну и, безусловно, всегда есть valgrind memcheck, который по своей сути является sandbox-ом памяти и с радостью покажет тебе не только утечку, но и любой heap corruption, любое неправомерное обращение к памяти, которое ты будешь просто не в состоянии отловить руками или глазами.
    Насколько я помню, за последние 2-3 года valgrind так и не стал кроссплатформенным.
    Ответ написан
    4 комментария
  • Что не даёт на 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 комментариев
  • Может ли код(определение) в заголовочных файлах быть вынесен в shared library?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    У C++ есть понятие Translation Unit. Как видишь, это 7я фаза трансляции, слияние всех .h файлов происходит на 4й фазе.
    .h файлы не являются единицами трансляции по своей семантике.

    В идеале, библиотека "fast-cpp-csv-parser" потому и выполнена в виде header-only, что она fast, используется по ее месту и не подразумевает за собой дополнительных единиц трансляции. Ее незачем выводить в динамическую библиотеку.

    Но если, скажем, тебе очень надо, то можно сделать так.
    Нужно объявить общий интерфейс взаимодействия между кодом процесса и кодом библиотеки (набор функций или интерфейс, по вкусу). В рамках единицы трансляции твоей динамической библиотеки, и вне этого общего интерфейса, надо подключить и задействовать "fast-cpp-csv-parser". Конкретная реализация общего интерфейса взаимодействия внутри твоей динамической библиотеки должна использовать код "fast-cpp-csv-parser".
    Ответ написан
    Комментировать
  • Какое время жизни у переменной?

    @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++17, if constexpr)?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ты можешь добиться желаемого используя частную специализацию, например.

    Суть такова. Нужно превратить значения типа перечисления в тип. Сделать мы это можем используя возможность указывать в качестве параметра шаблона значения перечислимых типов.

    Например:
    enum class AllowedTypes : uint8_t
    {
    	Int8	= 0,
    	Uint8,
    	Int16,
    	Uint16,
    };
    
    template< AllowedTypes TYPE >
    struct AllowedType;


    А для того, чтобы конкретное значение перечисления правильно переводилось в тип, шаблон "AllowedType" нужно специализировать.
    spoiler
    template<>
    struct AllowedType<AllowedTypes::Int8>
    {
    	using Type = int8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint8>
    {
    	using Type = uint8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Int16>
    {
    	using Type = int16_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint16>
    {
    	using Type = uint16_t;
    };


    После этого достаточно использовать "typename AllowedType::Type" в нужном тебе месте.
    spoiler
    template< AllowedTypes TYPE >
    struct spi
    {
    	typename AllowedType<TYPE>::Type*	p_tx = nullptr;
    	
    	constexpr spi() {};
    };


    Вот пример работы:
    spoiler
    // Example program
    #include <typeinfo>
    #include <iostream>
    #include <string>
    #include <cstdint>
    
    enum class AllowedTypes : uint8_t
    {
    	Int8	= 0,
    	Uint8,
    	Int16,
    	Uint16,
    };
    
    template< AllowedTypes TYPE >
    struct AllowedType;
    
    template<>
    struct AllowedType<AllowedTypes::Int8>
    {
    	using Type = int8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint8>
    {
    	using Type = uint8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Int16>
    {
    	using Type = int16_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint16>
    {
    	using Type = uint16_t;
    };
    
    template< AllowedTypes TYPE >
    using TypeFor = typename AllowedType<TYPE>::Type;
    
    
    template< AllowedTypes TYPE >
    struct spi
    {
    	TypeFor<TYPE>*	p_tx = nullptr;
    	
    	constexpr spi() {};
    };
    
    int main()
    {
    	spi<AllowedTypes::Int8> spi1;
    	std::cout << typeid( *spi1.p_tx ).name() << " " << sizeof( *spi1.p_tx ) << std::endl;
    	
    	spi<AllowedTypes::Uint16> spi2;
    	std::cout << typeid( *spi2.p_tx ).name() << " " << sizeof( *spi2.p_tx ) << std::endl;
    	
    	return 0;
    }

    cpp.sh/85p6
    Ответ написан
    2 комментария
  • Как совместить 2 языка С++ и C#?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    2. Писать на С++/CLI. Только тут я не понимаю, как их совместить.
    Ну написал я код на С++/CLI, а как мне его в проект , написанный на С# вставить ?


    Этот вариант неплохо разжеван в этих двух вопросах.
    Можно ли совместить в одном проекте программы написанные на разных языках (C++, C#) в VS2012?
    Как динамически подключать библиотеки?

    Этот вариант на деле оказался самым удачным, потому что деление проекта на три части (C++; CLI; C#) так же строго делит и области ответственности. На стороне C# остается только интерфейс, на стороне CLI - только связь с низким уровнем, на стороне C++ - только низкий уровень.
    Целостность кода сохраняется, библиотеку низкого уровня можно с равной легкостью подключить как к библиотеке CLI, так и к другому низкоуровневому проекту.
    Ответ написан
  • Как компилировать C++, JAVA?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для примера можно посмотреть вот этот сайтик: https://gcc.godbolt.org/
    Он доступен в открытом исходном коде: https://github.com/mattgodbolt/compiler-explorer

    Еще можно посмотреть вот эту репку: https://github.com/shawon100/Online-Compiler
    На гитхабе таких штук очень много. Если пороешься, скорее всего найдешь уже готовое для себя.
    Ответ написан
    Комментировать
  • Как динамически подключать библиотеки?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    lightalex , да, описанное тобой поведение возможно.
    У меня есть очень старый проект одной утилиты. Там практически реализовано необходимое тебе.

    Вот самая основа, которая может тебя заинтересовать.
    https://github.com/FrankStain/tex-conv/blob/master...
    Это - функция обнаружения и загрузки всех плагинов в определенной директории.

    https://github.com/FrankStain/tex-conv/blob/master...
    Это - функция выгрузки плагинов.
    Ответ написан
    4 комментария
  • Как передать указатель на метод экземпляра класса, как параметр конструктора?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Павел Каптур , а правильно будет - использовать примитивы проектирования. В частности - декоратор или просто интерфейс.

    А вот по поводу std::function и std::bind смотри пример - cpp.sh/56bak . У тебя плейсхолдера нехватает.
    Ответ написан
    Комментировать
  • Какая разница между ClassName::m_Field и this->m_Field в нестатическом методе?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Денис , есть такой термин, как Квалификация идентификатора. Соответственно, идентификатор может быть как полностью квалифицированным (fully qualified), так и частично (partially qualified). Обычно большая часть квалификации может быть автоматически подставлена во время трансляции кода. Однако, в целом ряде случаев нужно руками расширять квалификацию.

    Возьмем такой пример:
    spoiler
    class Foo
    {
    public:
    	void baz1( int bar )
    	{
    		this->bar = bar;
    	}
    	
    	void baz2( int bar )
    	{
    		Foo::bar = bar;
    	}
    	
    	void baz3( int bar )
    	{
    		this->Foo::bar = bar;
    	}
    
    public:
    	int bar = 0;
    };


    Имя параметра функций соответствует имени поля класса. Это плохой стиль, но для примера - самое то. Частично квалифицированное имя "bar" в любой из функций будет ссылаться на как можно более локальные данные. В данном случае - именно на параметр, а не на поле класса. Теперь, чтобы сказать "поле класса bar" нам надо сделать квалификацию имени "bar" более полной. Самая полная квалификация используется в функции "baz3".

    Любое имя может быть заменено на его полную квалификацию, включая и операторы. Можно написать "foo.operator->()" вместо "foo->", если класс "foo" имеет перегруженный оператор "->". Я не представляю зачем это нужно, но так тоже можно написать.

    Можно обратиться к перекрытой или виртуальной функции родительского класса.

    К слову, полная квалификация очень редко когда действительно нужна. Например, вот в этом шаблоне без расширения частичной квалификации поля "m_data" использовать его в наследнике не получится. А вот в этом шаблоне без полной квалификации имени не получится взять указатель на статическую функцию шаблонного класса.
    Ответ написан
    Комментировать
  • Что значит шаблон friend-функции внутри структуры?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    По всем этим вопросам к прочтению предлагаю следующую книгу: Дэвид Вандевурд, Николаи М. Джосаттис: Шаблоны C++....

    Сходу можно сказать что объявление дружественности оператора лишнее - в заблуждение вводит, преимуществ не дает. Внешняя перегрузка конкретно этого оператора в дружественности не нуждается.
    Проверка тут: cpp.sh/73htm

    По поводу конструкций вида "template< typename >" у Вандервуда написано так: "Поскольку шаблонный параметр параметра шаблона далее не используется, его имя можно опустить".
    Это равнозначно объявлению функции вида "void foo( int32_t );". Неиспользуемые идентификаторы можно не писать, но это снижает культуру кода.

    template <class > friend std::ostream& operator<<(std::ostream& s, Vec2<t>& v);

    Параметр опущен потому что не используется. Вместо него используется шаблонный параметр "t" из "Vec2".
    Это нужно для того, чтобы для любого конкретного "t" у "Vec2" был всего один дружественный оператор - инстанцированный от все того же "t".
    Ответ написан
    Комментировать
  • Как реализовать кроссплатформенное ПО с библиотеками для Windows/Linux?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Я с 2011 года занимаюсь совершенствованием игрового фреймворка. Весь его код написан на C++. Целевых платформ раньше было 5 (Win, MacOs, iOs, Android, Bada), теперь 4 (Bada закрылась же) с приглядом за Tizen, WinMo и, когда-нибудь, консолями.
    В общем, уровень требований к кроссплатформенности должен быть понятен. И вот как я этого добился.

    Большая часть кода написана на платформонезависимом C++. Весь платформозависимый код расщеплен на три слоя:
    - Нижний слой, общий интерфейс для всех платформ, общие поля всех платформ.
    - Средний слой, platform-specific решения и поля. Наследуется от нижнего.
    - Верхний слой, ввод platform-spcific кода во фреймворк. Наследуется от среднего слоя.

    Условная компиляция применяется только для включения заголовка платформозависимого кода. Никаких макросов, никакой условной компиляции больше не допускается. В платформозависимом коде все пишется открытым кодом так, как будто пишется под одну платформу.

    На уровне файлов этот подход работает так.
    Есть в заголовках проекта папка "platform", где собраны нижние уровни расщепления, мастер-заголовок с условным подключением среднего уровня расщепления и все общие типы для платформ.
    Так же в проекте есть папки "platform.windows", "platform.macos", "platform.###", в которых реализован средний уровень расщепления и мастер-заголовки для условного подключения.
    Верхний уровень или реализуется в своей папке, если он представляет собой целую подсистему, или описывается во все той же папке "platform".
    Исходный код сгруппирован так же, но включает в себя только мастер-заголовок .

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

    Это все дает полную прозрачность исполнения кода для любой платформы. Так же этот подход делает очень легкой экспансию всего фреймворка на новую платформу.

    UPD:
    Пример с файлом очень хорошо подходит благодаря своей простоте, его я даже по памяти могу выписать из своего фреймворка, но я кое-что все таки упрощу, чтобы никого не смущать и не пугать.
    spoiler
    // PlatformSpecificFile.Windows.h
    class PlatformSpecificFile
    {
    // Platform-specific interface.
    public:
    	inline ::HANDLE GetHandle() const	{ return m_handle; };
    	
    // Platform-independent interface, but platform-dependent implementation.
    public:
    	// RAII.
    	PlatformSpecificFile() = delete;
    	PlatformSpecificFile(
    		const std::string& path,
    		const OpeningMode desired_mode,
    		const AccessOptions& desired_access,
    		const SharingOptions& desired_sharing
    	);
    	
    	virtual ~PlatformSpecificFile();
    	
    	void Close();
    	void Flush();
    	
    	const size64_t GetSize() const;
    	const bool Resize( const size64_t new_size );
    	
    	const size32_t Read( NotNull<uint8_t> buffer, const size32_t buffer_size ) const;
    	const size32_t Write( NotNull<const uint8_t> buffer, const size32_t buffer_size );
    	
    	const size64_t Seek( const size64_t offset, const SeekOrientation orientation );
    	
    	inline const bool IsValid() const	{ return IsHandleValid( m_handle ); };
    	
    private:
    	::HANDLE	m_handle = INVALID_HANDLE_VALUE;
    };
    
    // PlatformSpecificFile.Android.h
    class PlatformSpecificFile
    {
    // Platform-specific interface.
    public:
    	inline int GetHandle() const	{ return m_handle; };
    	
    // Platform-independent interface, but platform-dependent implementation.
    public:
    	// RAII.
    	PlatformSpecificFile() = delete;
    	PlatformSpecificFile(
    		const std::string& path,
    		const OpeningMode desired_mode,
    		const AccessOptions& desired_access,
    		const SharingOptions& desired_sharing
    	);
    	
    	virtual ~PlatformSpecificFile();
    	
    	void Close();
    	void Flush();
    	
    	const size64_t GetSize() const;
    	const bool Resize( const size64_t new_size );
    	
    	const size32_t Read( NotNull<uint8_t> buffer, const size32_t buffer_size ) const;
    	const size32_t Write( NotNull<const uint8_t> buffer, const size32_t buffer_size );
    	
    	const size64_t Seek( const size64_t offset, const SeekOrientation orientation );
    	
    	inline const bool IsValid() const	{ return m_handle >= 0; };
    	
    private:
    	int		m_handle = -1;
    };
    
    // File.h
    class File final : public PlatformSpecificFile
    {
    public:
    	using PlatformSpecificFile::PlatformSpecificFile;
    	
    	const size64_t GetPosition() const; // Seek( 0, SeekOrientation::FromPosition );
    	
    	const bool SetPosition( const size64_t position ); // Seek( position, SeekOrientation::FromBeginning );
    	
    	const bool IsFileEnded() const; // GetPosition() == getSize();
    };


    Мастер-заголовок платформенного кода "platform.h" в зависимости от сценария сборки включает в себя один из мастер-заголовков платформозависимого кода "platform.###.h". Платформозависимый код уже включает в себя соответствующий заголовок файла "PlatformSpecificFile.###.h"
    Ответ написан
  • Можно ли совместить в одном проекте программы написанные на разных языках (C++, C#) в VS2012?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    https://github.com/FrankStain/tex-conv
    Это пример такого проекта. Многозадачный конвертер текстур.

    На верхнем уровне лежит C# приложение с UI на WinForms, вся работа с пользователем выполнена именно тут.
    К верхнему уровню подключается C++/CLI ядро, представляющее собой библиотеку классов. Это ядро связывает верхний уровень с нижним, который представлен потенциально несчетным набором плагинов.

    Библиотека классов в C# используется как будто это рядовой модуль.

    Конкретно для твоего случая я порекомендую поступить так.
    • Нижний уровень - проект статической библиотеки, в которой будет весь твой C++ код.
    • Средний уровень - C++/CLI библиотека классов, в которую и линкуется нижний уровень, свяжет нижний уровень с верхним через свои классы.
    • Высокий уровень C# приложение, использующее классы среднего уровня.
    Ответ написан
    Комментировать
  • Что такое анимация и с чем ее кушать если OpenGL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Spine C runtime.
    https://github.com/EsotericSoftware/spine-runtimes...
    Это не серебряная пуля и не идеальные практики. Пожалуй, в плане кода это пример наоборот, как код писать точно не надо.
    Но вот в плане реализации анимаций эта репка тебе очень поможет.

    Суть такова. Есть модель, она статична. Есть отрезок времени (таймлайн), на этом отрезке есть точки - ключевые кадры. Ключевой кадр несет в себе информацию о том, какую часть модели и как сместить. Чем проще инструкция в ключевом кадре, тем удобнее. Масштаб, поворот и смещение многие любят разделять по разным таймлайнам.
    OGL в этом деле не нужен, до поры.

    С помощью SRT таймлайнов можно анимировать объекты в пространстве целиком, но если тебе захочется точно так же анимировать части меша модели, то впереди тебя будут ждать трудности.
    Анимацию меша лучше реализовать на основе скелета. Это дело в двух словах уже не описать, тут лучше читать статьи.
    www.gamedev.ru/code/terms/SkeletalAnim
    www.gamedev.ru/code/articles/skeletal_animation
    https://habrahabr.ru/post/219509/
    https://habrahabr.ru/post/304042/
    www.gamedev.ru/code/articles/?id=4182

    Самое зерно скелетной анимации в том, что модель остается моделью, анимируются только кости скелета. И именно анимация кости приводит к перемещению фрагмента меша.

    Только на одной скелетной анимации далеко все равно не уедешь. Когда требуется на одной модели одновременно задействовать сразу несколько скелетных анимаций, если сделать в лоб, то меш поплывет во все стороны.
    Для смешивания различных скелетных анимаций применяют так называемые Blend Trees (ссылок под рукой нету, так что сорри).

    В общем, как то так анимация выглядит. Анимируем в цикле обновления, вершины трансформируем в момент формирования кадра. OGL нам нужен, собственно, чтобы нарисовать раскоряченый анимацией меш.
    OGL для непосредственного анимирования мешей тебе понадобится только тогда, когда ты достаточно глубоко нырнешь в анимации, в тот же момент ты уже полностью поймешь что от OGL понадобится для этого.
    Ответ написан
    2 комментария
  • Как реализовать наследование статического поля/метода, если это возможно?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    CodeInside , в твоем вопросе тебе поможет такой примитив проектирования, как Абстрактная Фабрика.

    Простая реализация на шаблоне и C++11 выглядит примерно так.
    spoiler
    template< typename TInterface, typename... TArguments >
    class AbstractFactory final
    {
    public:
    	// Produce the implementation, but return the pointer to interface.
    	inline std::shared_ptr<TInterface> Produce( const std::string& implementation_name, TArguments... arguments )
    	{
    		auto found_function = m_factory_functions.find( implementation_name );
    		return ( found_function == m_factory_functions.end() )? std::shared_ptr<TInterface>{} : found_function->second( std::forward<TArguments>( arguments )... );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation()
    	{
    		return DefineImplementation<TImplementation>( TImplementation::ClassName() );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation( const std::string& implementation_name )
    	{
    		// Abort the incorrect registration.
    		static_assert( std::is_base_of<TInterface, TImplementation>::value, "Implementation may only be derived from interface of Factory." );
    		
    		auto found_function = m_factory_functions.find( implementation_name );
    		if( found_function == m_factory_functions.end() )
    		{
    			m_factory_functions[ implementation_name ] = &AbstractFactory<TInterface, TArguments...>::template ConstructImplementation<TImplementation>;
    			return true;
    		};
    		
    		return false;
    	};
    	
    	// Check the implementation name is already defined.
    	inline const bool IsImplementationDefined( const std::string& implementation_name ) const
    	{
    		return m_factory_functions.find( implementation_name ) != m_factory_functions.end();
    	};
    	
    private:
    	// The factory function just produce implementation.
    	template< typename TImplementation >
    	static std::shared_ptr<TInterface> ConstructImplementation( TArguments... arguments )
    	{
    		return std::static_pointer_cast<TInterface>(
    			std::make_shared<TImplementation>( std::forward<TArguments>( arguments )... )
    		);
    	};
    
    private:
    	// Factory function produces the implementations of TInterface.
    	using FactoryFunction	= std::shared_ptr<TInterface> (*)( TArguments... arguments );
    	
    	std::unordered_map<std::string, FactoryFunction>	m_factory_functions;
    };


    Работает она примерно так:
    cpp.sh/93obm
    Ответ написан
    Комментировать