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

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

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

    В остальном, размещение всех нестатических полей класса производится внутри памяти создаваемого объекта.
    Ответ написан
    Комментировать
  • Как в 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 комментария
  • Как избавиться от привычки усложнять задачу?

    @MarkusD
    все время мелю чепуху :)
    Лично мне было бы интересно послушать подробности. В данной трактовке вопрос очень сложен из-за своей расплывчатости. :)
    Конкретнее! Нужна иллюстрация хода мыслей и критерии, по которым у тебя создается ощущение усложнения задачи.
    Так же очень важно понять, сам ты замечаешь усложнение или же тебе говорят об этом.
    Александр Синицын , дополни свой вопрос.

    Нередко в неадекватном усложнении задачи обвиняют именно те "соратники", кому попросту непонятна ни сама задача, ни ход твоих мыслей. Как правило это результат эффекта Даннинга-Крюгера.
    Ответ написан
    2 комментария
  • Какие задачи на C / C++ сейчас востребованы?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Понимаешь, практика показывает, что востребованным может стать абсолютно любая библиотека. Особенно если ты берешь на себя ответственность ее поддержки.
    Людей много, задачи они порождают все время и самые разные. Зачастую, количество потребностей в коде у человека превышает возможности его создания и поддержки. И фактор нехватки ресурсов (времени/рук) чаще всего играет решающую роль в отказе от идеи.

    К тому же, у художников вот есть такие места, как deviantart.com / cghub.com (был), где они выкладывают свои работы, по которым видно их рост и общий стаж в ремесле. Я считаю что и у разработчика тоже можно проследить тенденцию роста по его проектам на гитхабе, а по открытым доскам на trello.com можно еще и проследить уровень самоорганизации человека.

    Нашему IT рынку сейчас более нужны не гениальные раздолбаи, а собранные и самоорганизованные специалисты. Поэтому мой совет вот такой: лучше нацелиться на простые и организованные проекты из личного интереса, чем искать что либо выдающееся.
    Ответ написан
    Комментировать
  • Как решить проблему с ООП?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Да вы, батенька, король контента!

    Эт шутка. Хоть и не смешная для самоделкиных из области геймдева. :)
    Так, а кроме шуток. Не надо вообще так делать. Тип наносимого урона/снаряда/юнита/орудия никогда не должен описываться его собственным классом. Все юниты должны быть одного класса, все пули - тоже, все здания - тоже.
    У тебя должен быть только набор общих классов для сущностей. Конкретные параметры (что откуда вылетает, как дамажит и как это все визуально выглядит) должны лежать в ресурсах.
    Рекомендую почитать: habrahabr.ru/post/255561 Изложенное куда более важно для области любимых самокатов на С++.

    Хотя... Если тебе так надо реализовать на классах, а не на ресурсах, то тебе надо просто организовать фактическую связь между сущностями. Давай предложу один страшный вариант... Дебажить это все чур самому.
    class TowerParent
    {
    public:
    	// Это тип для той штуки, которая будет создавать нам нужную боеголовку.
    	typedef FireParent* (*FireConstructor)( float, float, int, float, float, float );
    	
    	TowerParent(int _col, int _row, float _posX, float _posY, float _attackRange, float _angle);
    
    	//...
    	
    	FireParent* towerFire;
    protected:
    	FireConstructor	fire_constructor; // инстанция конструктора боеголовок.
    	
    	// Вот так мы будем определять тип конструируемой боеголовки.
    	template< class fire_t >
    	inline void DefineFireType(){
    		fire_constructor = TowerParent::template MakeFire<fire_t>;
    	};
    	
    private:
    	// А вот так мы будем конструировать боеголовку.
    	template< class fire_t >
    	static FireParent* MakeFire( float _x, float _y, int _speed, float _angle, float _targetX, float _targetY ){
    		return new fire_t( _x, _y, _speed, _angle, _targetX, _targetY );
    	};
    };
    
    
    SimpleTower( int _col, int _row, float _posX, float _posY, float _attackRange, float _angle ){
    	DefineFireType<Bullet>();
    };
    
    RocketTower(int _col, int _row, float _posX, float _posY, float _attackRange, float _angle){
    	DefineFireType<Rocket>();
    };


    Конструирование делается через:
    towerFire = fire_constructor( .... );

    Еще одна поправка. Не Rocket, а Missile. Потому что ... :)

    UPD:
    Еще один вариант, уже менее страшный - это шаблонизация базового класса:
    template< class fire_t >
    class TowerParent
    {
    public:
    	TowerParent(int _col, int _row, float _posX, float _posY, float _attackRange, float _angle);
    
    	//...
    
    	fire_t* towerFire;
    
    protected:
    	inline fire_t* MkeFire( float _x, float _y, int _speed, float _angle, float _targetX, float _targetY ){
    		return new fire_t( _x, _y, _speed, _angle, _targetX, _targetY );
    	};
    };
    
    class Bullet : public FireParent<Bullet>
    {
    public:
    	Bullet(float _x, float _y, int _speed, float _angle, float _targetX, float _targetY);
    private:
    };
    
    class Rocket : public FireParent<Rocket>
    {
    public:
    	Rocket(float _x, float _y, int _speed, float _angle, float _targetX, float _targetY);
    private:
    };
    Ответ написан
    1 комментарий
  • C++ teplate class. Пoмогите! Почему вылетает segfault при попытке запихнуть std::string?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Штука первая, делать надо не так:
    array = (Type*) malloc(100);
    а так:
    array = new Type[ 100 ];
    или, лучше, вот так:
    array = static_cast<Type*>( new uint8_t[ 100 * sizeof( Type ) ] );


    Штука вторая. У тебя в выделенном блоке памяти мусор лежит. Надо сперва память подготовить. Делаем вот так:
    memset( array, 0, 100 * sizeof( Type ) );
    Это приведет к очистке памяти. Но и после этого память все еще нельзя использовать.

    Следом надо переделать вот так:
    void push_back(Type elem) {
        new( array ) Type();
        array[0] = elem;
      }

    или так:
    void push_back(Type elem) {
        new( array ) Type( elem );
      }
    Ответ написан
    Комментировать
  • Что должна вернуть функция NULL, если ничего не найдено?

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

    В С/С++ существует такой тип даных, как указатель на данные. Его синтаксис выглядит так:
    int* value;

    Запись буквально означает что 'value' у нас будет хранить адрес на область данных с предположительной типизацией в четырехбайтовое знаковое целое. Внимательно! 'value' хранит только указатель, никаких данных о размере блока этих данных нет, никаких строгих оговорок о типе этих данных нет (только предположение что это 'int').

    При формировании переменной оная, обычно, ничем никак не инициализируется. То есть, после определения нашего 'value' в его значении лежит любой немыслимый мусор. Внимательно! Нет никаких способов (кроме как обратиться и словить AV/SIGSEGV) определить что значение 'value' ссылается на правильный адрес. Поэтому этот самый мусор, который в 'value' и содержится, можно спокойно использовать как адрес блока данных и при обращении по этому адресу получить от ОСи по рукам.

    Вопрос! Как этого избежать?
    Есть очень простой и очень старый выход - определить некую магическую константу, которая точно смогла бы символизировать чистоту указателя (что указатель не одержит адреса). Именно такой константой 'NULL' и является.
    Адрес на блок данных может быть абсолютно любым! Он может быть даже 0xA0L. Но если в значении указателя записан 0 (это и есть NULL), значит указатель чист - он не содержит в себе адрес на блок данных.

    Итак! NULL - это не зло. NULL - это признак чистоты указателя!

    Теперь перейдем к "тонкостям и нюансам нолика"...

    Попробуй собрать такой код:
    typedef int* p_int;
    
    p_int value = p_int();
    printf( "ptr : 0x%08x\n", value );

    Вывод в консои будет : "ptr : 0x00000000". О чем это говорит? Это говорит о том, что возвращаемое инициализатором указателя значение (вот эта вот запись: "p_int()") всегда эквивалентно NULL.

    Можно было бы с точно таким же холоднокровием написать вот так:
    p_int value = NULL;
    И все осталось бы по прежнему.

    Дело в том, что NULL имеет тип "void*", а этот тип можно преобразовать абсолютно к любому иному указателю.
    И NULL самостоятельно приводится к нужному типу в операторе присвоения указателя этого типа. Ноль - он и в Африке ноль.

    Попробуй собрать такой код:
    int* value = NULL;
    delete value;


    Если бы 'value' небыл инициализирован, то с превеликой вероятностью оператор delete привел бы к падению приложения. 'delete' воспринял бы мусор как правильный указатель и попробовал бы освободить память по этому указателю, а так как это мусор, ОСь эту попытку забрила бы, выдав программе красную карточку. Вместе с тем, оператор delete спроектирован так, чтобы не обращать внимания на NULL. Оператор просто тихо завершается если видит нулевой адрес.
    Вывод: чистота NULL не нуждается в большей очистке! :)

    Вот так. Надеюсь, моя простыня текста хоть немного да поможет тебе.

    А по вопросу о функции - да возвращай NULL! Так все делают, чем ты хуже? ;)
    Ответ написан
    Комментировать
  • Какие ЯП не требуют кучу прикладнухи для устройства на работу?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    LUA скриптер может знать только LUA и работать без затруднений.
    Python скриптер может знать только свой ЯП и спокойно работать.

    Уяснить тебе стоит одну очень важную вещь. Один лишь нужный ЯП знать для работы может только скриптер.
    Для разработчика-же крайне важны знания как можно более широкой информационной периферии своей области.
    Ищи игровые студии/компании которым нужны LUA/Python скриптеры. Но запомнить надо еще одну вещь - для очень многих людей это дорога без возврата.
    Ответ написан
    1 комментарий
  • Какой вариант оптимальней?

    @MarkusD
    все время мелю чепуху :)
    По первому блоку:
    switch( b )
    {
    case 4:
    	GiveMe();
    case 3:
    	CallMe();
    	break;
    };

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

    Касательно второго блока: предлагаю самостоятельно собрать в дебаге и релизе следующий код, да убедиться в сомнениях самому.
    class A
    {
    public:
    	inline size_t begin()
    	{
    		printf( "begin();\n" );
    		return 0;
    	};
    	
    	inline size_t end()
    	{
    		printf( "end();\n" );
    		return 10;
    	};
    };
    
    A cont;
    for( size_t iter = cont.begin(); cont.end() > iter; ++iter )
    {
    	printf( "%d\n", iter );
    };


    Но тут всегда надо понимать, на какие траты допустимо идти ради удобства. Не стоит увлекаться вторыми переменными в объявлении for если тот же "end()" является легкой и оптимизируемой компилятором конструкцией. В этой связи советую уделить куда большее внимание области инкремента в "for".
    Постфиксная форма всегда приводит к созданию временной переменной, префиксная - нет. Префиксные формы для счетчиков циклов в Google Code Style помечены как хорошая практика.
    Ответ написан
    Комментировать
  • Каким путем развиваться как специалист в геймдеве?

    @MarkusD
    все время мелю чепуху :)
    За плечами 2,5 курса универа, более менее вменяемо освоен язык С++ и есть понимание как пишутся программы.

    Ты уж прости, в моих словах нет ни капли неуважения, только скепсис. Первое что надо понять - так это что плюсы у тебя не освоены даже на начальном уровне. Нужно подготовиться к тому, что на рабочем месте надо будет сперва забыть все из универа, а потом научиться заново, уже специализируясь в производстве. Для многих людей этот момент является психологическим барьером в начале работы.

    Так вот, нужен ли я буду с знаниями как программируется сама графика и движки, но без умения работать с готовыми решениями?

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

    Ты молодой человек, дело ясное что руки у тебя чешутся за все сразу взяться. Попробуй решить свой вопрос кратковременными занятиями во всех направлениях. Unity тебе поможет быстро вополтить игру в прототипе. Разные MDK к существующим играм помогут со скриптингом/модингом. Пакеты типа SDL/Cocos2d вполне смогут помочь тебе быстро поднять окружение для изучения невидимых с верхнего уровня фишек, типа вот таких:
    habrahabr.ru/post/241760
    habrahabr.ru/post/248381
    habrahabr.ru/post/250467
    habrahabr.ru/post/238425
    habrahabr.ru/post/248313
    habrahabr.ru/post/244367

    А важны всегда все люди. Точно сказать не получится: важнее ли ипользователи UDK чем его создатели. Каждый просто занял свое место в индустрии.
    Ответ написан
    Комментировать
  • Можно ли перегружать базовые типы в С++?

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

    Добавить поддержку шаблонных компараторов можно таким образом:
    template< typename item_t, template< typename > class item_traits >
    class item_processor{
    private:
    	typedef item_traits<item_t> traits_t;
    	...
    };


    Изюмина во втором параметре шаблона - это шаблонный параметр шаблона. :)
    У Девида Вандервуда по этому поводу было даже так написано: "Поскольку параметр шаблонного параметра шаблона не используется, его имя можно опустить".
    Такая запись говорит о том, что при инстанцировании или специализации шаблона в его объявлении ожидается только имя шаблона, а не его инстанцирование.

    Используется это дело вот так:
    template< typename target_t >
    class traits_less;
    
    ...
    
    item_processor<char, traits_less> char_processor;


    Все компараторы STL имеют один параметр шаблона. Таким образом, их использование тоже становится допустимо в качестве параметра для item_processor. В то же время, и свой компаратор в такую систему добавить проблемы не составит.
    Ответ написан
    Комментировать
  • Как загрузить DDS файл в c++ builder?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    DDS - Direct Draw Surface - это формат контейнера, заполненного одним или несколькими изображениями, снабженного специальным DDS заголовком.
    Как правило, в таком формате хранятся сжатые аппаратно-декодируемыми методами текстуры (S3TC, PVR, ATC, ETC). Вместе с тем, контейнер позволяет хранить и несжатые цветовые данные.
    Тип метода сжатия указан в DDS-заголовке, в полле "_fourcc", в виде четырехсимвольной строки.

    Пример описания DDS-заголовка:
    https://github.com/FrankStain/tex-conv/blob/master...

    Пример непосредственной загрузки DDS файла:
    https://github.com/FrankStain/tex-conv/blob/master...

    При загрузке DDS-файла, первым этапом читаем заголовок, все что дальше заголовка - это изображени(е|я).
    Если в заголовке файла указан формат сжатия данных, то перед их ручным использованием данные надо распаковать. Несжатые данные (RGB(1|4|8)(A) форматы) можно использовать прямо из файла.
    Писать распаковщик формата сжатия своими руками - дело затруднительное. Легче взять готовые библиотеки у непосредственных разработчиков или посредников этих форматов.
    ATC распаковывается с помощью Adreno SDK ( https://developer.qualcomm.com/download ).
    PVR распаковывается с помощью PowerVR SDK ( community.imgtec.com/developers/powervr/graphics-sdk ).
    DXT или S3TC обрабатывают как оба вышеперечисленных пакета, так и DirectX SDK. Так же существует набор утилит от nVidia - Texture Tools ( https://developer.nvidia.com/legacy-texture-tools ).
    Ответ написан
    Комментировать