Ответы пользователя по тегу C++
  • Как в C++ скрыть определение вспомогательных типов?

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

    Помимо всей этой воды, что я ниже изложил, еще очень стоит ознакомиться с разделом "SF: Source files" официального гайдлайна:
    https://github.com/isocpp/CppCoreGuidelines/blob/m...
    (Очень советую изучить весь гайдлайн от корки до корки)

    По первому вопросу, коротко: если тип используется по значению - полное объявление типа обязательно, иначе можно обойтись Forward declaration.

    Немного подробнее:

    Существует такой принцип формирования проекта, когда каждый заголовочный файл предоставляет полную информацию о зависимостях. Forward declaration в таком случае или запрещено, или сильно порицается. Каждый заголовочный файл должен обязательно включать в себя заголовочные файлы всех зависимостей. А файл исходного кода должен включать ровно один заголовок - тот, чей интерфейс реализуется в исходном коде.
    Это позволяет сразу видеть все зависимости кода, не париться с размещением файлов, не париться с транзитивными зависимостями, просто не париться, а так же существенно огребать на времени компиляции. Особенно если в проекте разрешен только #include "", а #include <> порицается.
    В качестве примера можно почитать UE4.

    Существует и другой принцип формирования проекта, когда файловая структура отражает макроуровень архитектуры проекта. Скажем, если проект базируется на подсистемах или слоях, каждый слой(подсистема) представляется ровно одним заголовочным мастер-файлом и своей отдельной папкой в файловой системе. Внутри этой папки расположены все внутренние заголовочные файлы подсистемы. Ни один внутренний заголовочный файл не содержит никаких зависимостей, только объявление своего интерфейса. Все зависимости (FD, другие подсистемы или слои, системные заголовки и.т.д.) описывает только мастер-файл, он же в нужной последовательности подключает все внутренние заголовки. Другие подсистемы, равно как и исходный код самой подсистемы, включают в себя только мастер-файл.
    Это позволяет снять всю шелуху с внутренних заголовков и сосредоточить их текст ровно на декларации интерфейса. С другой стороны, ради использования какого-нибудь мелкого типчика всегда приходится подключать всю подсистему, т.к. внутренние заголовки подключать нельзя по правилам формирования проекта и потому что их зависимости неясны.

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

    С точки зрения компилятора есть только один формат файла - формат исходного кода, который ему и надо обработать.
    С точки зрения человека форматов файла не два, а 3 или 4:
    • .c , .cc , .cxx , .cpp , c++ - формат исходного кода, в котором стоит производить определение интерфейсов и держать все приватные инструменты (код и типы);
    • .h , .hh , .hpp - формат заголовка, в котором подключаются заголовки зависимостей и объявляется интерфейс - ровно то, что может понадобиться в другом коде или не может быть определено в файле исходного кода. И ничего больше;
    • .inl - формат вспомогательного заголовка, в котором производится определение inline функций и сложных шаблонных конструкций;
    • .inc - формат вспомогательного заголовка, в котором описываются форварды, внешние глобальные переменные, константы и прочие данные. Этот формат используется реже всего. Вместо него чаще используют формат заголовка (.h), размещая в нем весь контент .inc файла.


    Если с "человеческими форматами" все должно быть хорошо понятно, то с форматом файла для компилятора стоит уяснить одну тонкость - все эти человеческие шахматы с бубнами и делением на файлы должны складываться в как можно более удобный для компиляции вид. Чем меньше одинаковых #include, тем лучше. Чем меньше #include в целом, тем лучше. Трансляция - дело итак нелегкое.
    Ответ написан
    Комментировать
  • Можно ли так инициировать компоненты класса?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    en.cppreference.com/w/cpp/language/class
    Параграф "Member specification".
    Если ты используешь стандарт c++11 и выше, то инициализация полей непреложна.

    Единственным исключением будет список инициализации полей в пользовательском конструкторе.
    en.cppreference.com/w/cpp/language/initializer_list
    Ответ написан
    Комментировать
  • Всегда ли в C++ false == 0, true == 1?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В стандарте по этому поводу все однозначно.
    eelis.net/c++draft/conv.prom#6
    Ответ написан
    3 комментария
  • Как с помощью constexpr C++11 создать const объект, который бы содержал внутри себя массив (const) указателей на объекты другого класса?

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

    Есть техника статической цепочки, ее суть сводится к тому, чтобы на этапе компиляции создать односвязный список объектов, к которым в рантайме требудется получать доступ. Эта техника хорошо реализована в DOOM3 для декларации всех опций движка.
    Корень статической цепочки в думе находится вот тут: https://github.com/TTimo/doom3.gpl/blob/master/neo...

    А регистрация опций делается так:
    https://github.com/TTimo/doom3.gpl/blob/master/neo...

    Как видишь, статическая цепочка нужна больше для хранения экземпляров общего интерфейса.
    А если тебе надо просто собрать группу статически инициализированных объектов одного типа, то сойти может и такое решение:
    cpp.sh/8eco
    Ответ написан
    Комментировать
  • Почему неправильно считывается системное время?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    https://msdn.microsoft.com/en-us/library/1f4c8f33.aspx

    Все дело в том, что функция time() возвращает UTC метку времени. То есть, время по Гринвичу.
    Тебе надо сделать корректировку на свой часовой пояс для получения локального времени.

    Посмотри пример по этой ссылке:
    en.cppreference.com/w/cpp/chrono/c/gmtime
    Ответ написан
    Комментировать
  • Как добавить поддержку ZLIB в Android Studio ( C++ )?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    cannot find -llz

    Память мне подсказывает что не "-llz", а "-lz". :)
    Ответ написан
  • Как исправить ошибку 'сбой при специализации функции-шаблона' (многопоточность)?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    en.cppreference.com/w/cpp/thread/thread/thread

    int run(); // Имеет тип `int (FirstClass::*)()`. А конструктору `std::thread` нужен или функтор, или глобальная функция.


    Перепиши `run()` на статическую и передавай инстанцию объекта `FirstClass` параметром при запуске.
    иное решение - использовать лямбду с замыканием на this.
    FirstClass::FirstClass() : /* список инициализаторов */ firstClassThread([this](){ run(); }) { }


    Имей в виду что поток запустится сразу же после завершения конструктора `std::thread`.

    en.cppreference.com/w/cpp/thread/thread
    Threads begin execution immediately upon construction of the associated thread object (pending any OS scheduling delays), starting at the top-level function provided as a constructor argument.

    Буквально, поток запускается сразу после его конструирования.

    en.cppreference.com/w/cpp/thread/thread/detach
    Separates the thread of execution from the thread object, allowing execution to continue independently.

    А вот `detach` ничего с запуском потока, или с самим потоком, не делает, она только сбрасывает состояние объекта потока, обрывает связь между объектом потока и самим потоком.

    Может тебе стоит как-то иначе запускать поток, в другое время? Если тебе все таки надо запустить поток в другое время, его в это время и надо конструировать.
    Ответ написан
    2 комментария
  • Есть ли в stl такой мьютекс, который можно разблокировать из другого потока?

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

    На основе этой сущности можно реализовать механику "wait() -> notify/notifyall()" из pthread_monitor или из Java.
    Ответ написан
    2 комментария
  • Как исправить ошибку 'map итератор неразыменовываемый'?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    if ((!words.empty() && words.find(word) != words.end() && word_str != words.end()->first) || std::string(word) == words.end()->first)

    Условие страшное, отвыкай так писать. Разделяй на поясняющие переменные и группируй проверки скобками.

    А проблема в words.end()->first.
    en.cppreference.com/w/cpp/container/map/end
    Итератор, возвращаемый end() не может быть разыменован.

    UPD:
    Задача, как я вижу, собеседовательная. У нее есть более простое и быстрое решение с меньшим количеством строк.
    Ответ написан
    4 комментария
  • Какой смысл в фильтрах Visual Studio?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Фильтры в Visual C++ - это методика организации рабочего пространства. Это старая и проверенная методика, зарекомендовавшая себя в MSVC++ еще с очень старых времен.
    Система фильтров позволяет организовать любую лапшу из файлов сторонней библиотеки, в которой все сделано по принципу: "Работает == Не трогай". Этим она чрезвычайно полезна.
    Так же фильтры позволяют сделать более тонкую организацию в тех местах, где на уровне файловой структуры она бессмысленна.

    Я бы не был так поспешен, ругая инструмент за то, что им пользуются явно неорганизованные или неграмотные "специалисты". В подавляющем большинстве проектов ты просто видел невежество. И ничего больше.
    Да, фильтры никак не привязаны к реальным папкам, а это и не надо. Да, на диске все можно хранить в одной папке (и в этом реальный плюс фильтров). Да, фильтры позволяют организоваться только внутри пространства самой среды.
    Но и ничего больше.
    Многие люди не считают должным разделять заголовки и исходный код по разным папкам, пеняя на "слишком сложную" организацию Include Directories. Только это не проблема среды, это такой у людей стиль организации рабочего пространства.
    Ответ написан
    3 комментария
  • Как получить изображение максимально малого объёма для микроконтроллеров?

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

    Для картинки считается количество уникальных цветов, потом все уникальные цвета складываются в палитру и индексируются, а матрица цветов заменяется на матрицу индексов цветов.
    Число цветов в палитре принято делать от степени двойки, чаще всего встречаются палитры в 256 цветов. В этом случае размер индекса цвета в матрице занимает 1 байт. 16 цветов в палитре - индекс уже 4 байта. и.т.д.
    Прозрачность в этом случае неудобств не доставляет, т.к. в палитре можно спокойно хранить цвет в формате RGBA.

    Когда число цветов нужно вписать в некоторые рамки, для этого применяют разные алгоритмы так называемого dithering.
    Ответ написан
    Комментировать
  • Как реализовать частичную специализацию для указателя на определённый класс?

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

    Чтобы понять преимущество предлагаемого мной подхода, нужно обратиться к коду std::basic_string.
    Например, вот эта реализация.
    https://android.googlesource.com/platform/ndk/+/bf...

    Вторым параметром идут так называемые черты - traits для типа символов в строке. По своей сути функциональность std::string, std::wstring, std::u16string и std::u32string отличается только реализацией именно этого параметра.
    Сам шаблон черт для строки описан немногим выше 75й строки.
    Вся реализация std::basic_string полностью опирается на список статических функций из заданных в шаблоне черт.

    Я предлагаю вынести частную специализацию за пределы твоего шаблона List и определить черты ListTraits, которые уже и специализировать для нужных классов, в частности - для Storage_device.
    В черты можно и нужно выделять только важные функции. Это можно сравнить с вынужденной мерой.

    template< typename TDataType >
    class ListTraits
    {
    public:
    	// ...
    	
    	template< typename TNodeType >
    	inline static void AddHead( TNodeType node, TDataType data );
    	
    	// ...
    };
    
    template<>
    class ListTraits<Storage_device*>
    {
    public:
    	// ...
    	
    	template< typename TNodeType >
    	inline static void AddHead( TNodeType node, Storage_device* data );
    	
    	// ...
    };
    
    template< typename TNodeType, typename TDataType >
    class List
    {
    	// Тип используемых черт.
    	using Traits = ListTraits<TDataType>;
    	
    	TNodeType *head, *tail;
    	int count;
    
    public:
    	// ...
    	
    	void AddHead(TDataType data);
    	
    	// ...
    };
    
    template< typename TNodeType, typename TDataType >
    void List<TNodeType, TDataType>::AddHead( TDataType data )
    {
    	// ...
    	
    	Traits::template AddHead( temp, data );
    	
    	// ...
    };
    Ответ написан
    Комментировать
  • Что происходит с переменной / указателем в памяти после его обнуления?

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

    Что произойдет после данной вышеописанной операцией с членом size и указателем старого объекта (other)? Они останутся в памяти вместе с объектом? Или при нулевом значение они занимают незначительную область памяти?


    Давай обратимся к C++ Core Guidelines за разъяснениями.
    Советую, кстати, детально изучить от корки до корки.

    https://github.com/isocpp/CppCoreGuidelines/blob/m...
    В результате перемещения источник должен оставаться в состоянии, когда им можно дальше пользоваться.

    Наш объект класса SomeClass может быть выделен как на стеке, так и в куче. Когда мы его перемещаем, мы перемещаем только внутренности объекта, а не сам объект. После перемещения объект все так же остается жить и должен иметь возможность правильно удалиться как со стека, так и из кучи. Ответственность за это полностью лежит на разработчике класса. Но удаляет объект не семантика перемещения и, тем более, не конструктор перемещения. функция std::move является всего лишь декоратором, который меняет тип объекта с lvalue на rvalue. Она не занимается удалением или очисткой объекта.

    После перемещения объект можно дальше использовать. Если следовать гайдлайну, то после перемещения объект должен стать таким, как будто он был только что сконструирован стандартным конструктором. Объект можно снова заполнить и снова переместить. И так - сколько угодно раз.
    А сам объект всегда занимает один и тот же объем памяти, определенный его классом.

    Так же по теме:
    https://github.com/isocpp/CppCoreGuidelines/blob/m...
    https://github.com/isocpp/CppCoreGuidelines/blob/m...
    Ответ написан
    2 комментария
  • Есть ли какой нибудь удобный класс в библиотеках c++ для работы с датами?

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

    Почитать можно тут:
    en.cppreference.com/w/cpp/chrono/time_point
    Ответ написан
    Комментировать
  • Использование вариативного шаблона функции без аргументов?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Смотри... Т.к. в предложенном коде я уже вижу немного бардака, то и вариант предлагаю слегка бардачный. :)
    Эту задачу можно решить сразу несколькими способами метапрограммирования. Предложенное решение - одно из них.
    template< typename TComponentType >
    bool HasComponent()
    {
    	return ...;
    };
    
    template< typename... TComponentTypes >
    bool HasComponents()
    {
    	// Это тип предиката для проверки наличия одного компонента.
    	using CheckingPred	= bool (*)();
    	
    	// Это тип листа проверки.
    	using PredList		= std::array<CheckingPred, sizeof...( TComponentTypes )>;
    	
    	// Определяем лист проверки, следи за руками... :-)
    	PredList predicates{{ HasComponent<TComponentTypes>... }};
    	
    	// Проверяем!
    	return std::all_of( predicates.begin(), predicates.end(), []( CheckingPred stored_pred ) -> bool { return stored_pred(); } );
    };


    Сразу заостряю внимание на форматировании и стиле. Так сказать, что понятнее и удобнее читается? :)

    UPD:
    Еще предлагаю полистать библиотеку одного интересного товарища: https://github.com/alecthomas/entityx
    Как я понимаю, тебе хочется попробовать реализовать ECS. EntityX - одна из самых прямых ECS библиотек на плюсах, если не сказать - самая прямая.
    Ответ написан
  • Как посмотреть результат работы линкера?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для этих целей есть целый набор утилит. Всякие пакеты для разработки обычно кладут эти инструменты рядом с GCC.
    Вот, первое что вспомнилось: https://sourceware.org/binutils/docs/binutils/objd...
    Думаю, даже если это не то, то там ты найдешь нужный тебе инструмент.
    Ответ написан
    Комментировать
  • Как получил указатель на экзепляробъекта?

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

    Литературка для чтения.
    en.cppreference.com/w/cpp/language/value_category

    Коротко.
    Объявление типа `int` - тип с хранением по значению.
    Объявление типа `int*` - тип с хранением по указателю.
    Объявление типа `int&` - тип с хранением по ссылке.

    Любая из этих форм записи типа может быть использована для определения переменных, параметров и результатов функций.

    for (auto card : hand)
    Говорит что card принимается по значению через копирование содержимого итератора. Время жизни card ограничено пространством цикла.

    for (auto& card : hand)
    Говорит что card принимается по ссылке содержимого итератора.
    Ответ написан
  • Какой способ организации чтения и записи файлов в разных форматах оптимальнее?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Мы используем Flatbuffers. :)
    Документация присутствует.
    Эта штука и версионирование данных поддерживает, при соблюдении некоторых условий.
    Ответ написан
  • Какими приёмами вы пользуетесь чтобы различать указатели, которые нужно освобождать, от указателей, которыми нужно только пользоваться?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    pixik , витиеватый, очень размытый вопрос. Что же тебя все-таки интересует? Как другие люди организуют схемы владения? Или каким образом лучше организовать удаление объекта при разделенном владении.
    Тебе суть вопроса, скорее всего, понятна. Но это лишь потому что у тебя есть контекст этого вопроса, а у всех остальных его нет и он явно не создается.

    Так вот.
    Если один объект (A) безраздельно владеет другим (B), то второму положено быть частью первого. Так просто легче жить.
    Если вторым (B) является указатель, то в первом (A) используется std::unique_ptr опять-же для упрощения себе жизни.
    При этом, вложенный объект (B) из родительского отдается по голому указателю. Ну да, это норма, хоть мои слова и встретят бурю негодования нетерпеливых читателей.
    В это же самое время появляется риск схлопотать AV/SEGV когда для голого указателя (B) появляются клиенты (C), время жизни которых значительно больше времени жизни (A). Все просто, (A) живет меньше семейства (C), следовательно сохраненный в (C) голый указатель на (B) обязательно приведет к AV/SEGV после окончания времени жизни (A).

    Вот, проблема оглашена. Решаем.

    Решение первое: std::shared_ptr и std::weak_ptr.
    std::shared_ptr является примитивом разделения владения. Будем хранить (B) внутри него. А наружу (A) будет отдавать st::weak_ptr с указателем на (B). Клиенты семейства (C) будут держать (B) внутри сохраненных std::weak_ptr. Таким образом все семейство (С) всегда будет защищено от обращения к удаленному (B).

    Решение второе: два std::sared_ptr.
    Ну мало ли... Я знаю несколько случаев когда именно такая схема является максимально эффективной. В частности, бывает необходимо сделать именно разделенное владение внутри всего семейства (C), давая информацию для (A) о нужности оставлять (B) в живых. Пулы, кеши по владению, мало ли таких задач...
    Эта схема достаточно сложна и требует содержать std::weak_ptr на каждый внешний std::shared_ptr для обеспечения правильной работы схемы.
    Решение сводится к тому, чтобы уже имеющийся std::shared_ptr обернуть в другой с заменой деконструктора.
    (A) отдает (B) через std::shared_ptr, но в момент запроса (A) оборачивает реальный std::shared_ptr (B) в новый, используя голый указатель на (B) и свой сторонний деконструктор. Внутри этого стороннего деконструктора делаются все нужные манипуляции над (B). Этот сторонний деконструктор вызовется тогда, когда все семейство (C) избавится от ссылки на (B).

    Решение третье: когда (A) сам лежит внутри std::shared_ptr и унаследован от std::enable_shared_from_this.
    Тут все просто, для выдачи (B) из (A) можно использовать делегируемый std::shared_ptr, который сконструирован из указателя на (B) и std::shared_ptr (A) полученного из функции shared_from_this().
    В этом случае (A) не помрет до тех пор, пока семейство (C) не отпустит (B).

    Как видишь, корень твоего вопроса решается всеми тремя способами. На самом деле способов еще больше, но самое важное что все способы дают стандартное решение. Велосипеды в этом вопросе - это от неграмотности.

    Советую максимально подробно изучить раздел управления памятью в STL: en.cppreference.com/w/cpp/memory

    Принимаю вопросы и уточнения. :)
    Ответ написан
  • Как лучше организовать многопоточную архитектуру?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Смотри, прямо готового монолитного шаблона пожалуй нет (точнее, я со своего опыта такой не нахожу).
    По описанию видно что можно использовать Thread pool + Proactor + Actor model.
    Вместо потока на девайс будет пул потоков + управляющий поток, т.е. семейства потоков только два. Каждый девайс - актор. Действия актора выполняются атомарно в необходимом потоке (главный/сервисный) благодаря планированию нужного обработчика для нужного потока.

    Вот, подкину литературку:
    www.gamedev.ru/code/articles/concurrency_models
    https://habrahabr.ru/users/eao197/topics/ - SObjectizer является довольно интересной библиотекой.

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