𝐑𝐨𝐨𝐭𝐓𝐨𝐨𝐥 𝐁𝐥𝐨𝐠
194 subscribers
570 photos
48 videos
5 files
60 links
ニャン
Download Telegram
А потом выясняется что весь проект - это 1 файл на 10 тысяч строчек кода без документации или хотя бы намека на комментарии 😁
😱5❤1🤣1🤓1
Искусство Построения Архитектуры Проекта

Глава 4: "Квантовая запутанность" классов и "теория большого #includ'а"

Вспомним:
- Глава 2 - #include "Path/File.h" - открывает файл по пути и жестко заменяет себя на его содержимое
- Глава 3 - Forward Declaration в связке с .h файлом позволяет в других файлах только объявить функцию, а в одном конкретном .cpp файле реализовать ее. Затем линкер совместит все нереализованные функции с их реализациями (Глава 0)

Не буду душнить теорией, начнем сразу с кейса: У нас есть класс Player и World которые взаимосвязаны - игрок может получить мир в котором находится, мир может узнать что за игрок в нем живет. Составим код:
// Player.h

class Player {
World* World;
}

// World.h

class World {
Player* Player;
}

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

Какова идея? Вспомним с Главы 2, что мы можем подключить .h файл в котором будут объявлены классы/функции. Сделаем:
// Player.h
#include "World.h"

class Player {
World* World;
};

// World.h
#include "Player.h"

class World {
Player* Player;
};

^ И вот тут мы получаем уроборос .h файлов, ибо взглянем это с препроцессора:
↓ Он открывает Player.h
↓ Внутри Player.h есть #include "World.h"
↓ Он открывает World.h, заменяет строку на его содержимое
↓ Внутри World.h есть #include "Player.h"
↓ Он открывает Player.h, заменяет строку на его содержимое
↓ Внутри Player.h есть #include "World.h"
↓ Он открывает World.h, заменяет строку на его содержимое
↓ Внутри World.h есть #include "Player.h"...
↓ ...
Безумие, не так ли? В этом и суть Циклической зависимости (Circular Dependency) которую мы только что открыли! Но и у этого есть пару решений

Еще в Главе 2 я упомянул про #pragma once - что ж, это его звездный час! Ибо #pragma once так и говорит препроцессору - этот .h файл подключать ТОЛЬКО 1 РАЗ! Не более! И обычно эта директива пишется в начале каждого .h файла, то есть:
// Player.h
#pragma once
#include "World.h"

class Player {
World* World;
};

// World.h
#pragma once
#include "Player.h"

class World {
Player* Player;
};

И вот теперь при препроцессинге в файле Player.h подключится/раскроется World.h и на этом цикл будет оборван из-за #pragma once вначале Player.h - магия! Но мы лишь остановили признак (рекурсию), но не решили проблему. Взглянем на Player.h после препроцессинга:
// Player_AfterPreprocessing.h
#pragma once

// #include "World.h"
// ↓ - Начало "World.h"
#pragma once
// #include "Player.h"
// Блокируется из-за #pragma once
// В начале файла "Player.h"

class World {
Player* Player; // ?
};
// ↑ - Конец "World.h"

// Остальной код
class Player {
World* World;
};

Но подождите-ка, как класс World должен знать об Player, если он объявлен чуть ниже? Мда.. И тут неожиданно на помощь приходит Forward Declaration с прошлой главы, ну конечно же! Если мы вначале просто объявим/упомянем что есть такой-то класс/функция, то при линковке они найдут друг-друга, поздравляю! Это и было решением и оптимизацией в тоже время:
// Player.h
#pragma once

class World;
// Forward Declaration

class Player {
World* World;
};

// World.h
#pragma once

class Player;
// Forward Declaration too

class World {
Player* Player;
};

Но подождите, почему именно оптимизацией? По опыту: #include крайне грубая штука - он раскрывает все файлы рекурсивно и если, например, в проекте есть 1 .h файл который имеет 20 #includ'ов внутри, то при использовании этого .h вместе с ним будут раскрываться И ТЕ 20 .h ФАЙЛОВ! То есть да: #include - удобный, но имеет накопительный эффект. И именно поэтому использование Forward Declaration лучшее решение из возможных

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

Break The Chain. Think Cold.
❤‍🔥2😱1
This media is not supported in your browser
VIEW IN TELEGRAM
❤‍🔥2🕊1
Розыгрыш багов на Альфу 1.1.0
Кто найдет - тому 10 лет условно 💀

Успей получить свой долгожданный отпуск!

GameJolt: тык
❤3🤩2❤‍🔥1😭1😎1
Искусство Построения Архитектуры Проекта

Глава 5: inline-функции и правило одной реализации

Данная глава будет маленькой, но крайне полезной. Основной вопрос: Можно ли реализовывать функции в .h файле?
Ответ: Да, но с оговорками

Вы сделали это:
// Test.h
#pragma once

int Add(int a, int b) {
return a + b;
}

Что тогда? Ну... Во-первых компилятор вам банально не даст этого сделать - сработает намеренная защита. Представьте, что вы подключите этот Test.h файл в нескольких .cpp файлах -> Препроцессор раскроет #include "Test.h" -> В нескольких .cpp файлах будет int Add(...) {...} (прошу заметить - с реализацией!) - и когда линкер начнет сшивать .obj файлы вместе, то в каждом из них будет объявлена и реализована функция Add, и вопрос: Какую реализацию ему выбрать? Вот именно...

Так что нам придумали одно правило: Правило Одного Определения (ODR - One Definition Rule)

"А если я хочу намеренно реализовать функцию в
.h файле?" - Для нас дядя Бьёрн позаботился и придумал пометку inline - это пометка компилятору, которая говорит: вставь код функции напрямую в место её вызова вместо выполнения традиционного вызова. То есть:
// Test.h
#pragma once

inline int Add(int a, int b) {
return a + b;
}

Что это дает? При компиляции, эта функция по факту не будет создана, а ее машинный код будет вшит в тех местах, где она вызывается. То есть это такой "своеобразный #include для функций". И главное: при той же линковке, функция Add по факту не будет создана, а значит не будет проблем с ODR

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

Но и у этого есть оттягивающий эффект: Я не зря сравнил inline с #include - ибо он также имеет накопительный эффект: Чем больше функций будут раскрываться - тем крупнее будет бинарник. А также учтите ещё размер функции, и получите охерительно жирный бинарник и несколько часов компиляции. Бинго!

В любом случае, как и говорилось ранее, главное разобраться в этой теме и сделать выводы. Я не говорю не использовать эту механику. Наоборот, даже в своем проекте Portal: Solver у меня есть отдельный файл утилит (PortalSolverUtilities.h) в которых реализованы небольшие inline функции-помощники и используются при необходимости. Но реализовывать целый движок в единственном .h... Мда... Надеюсь теперь вы понимаете цену своих действий

ODR Looks Like Error - But It's Just Protection From Chaos. Think Cold.
🔥4❤‍🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
❤3
Portal: Solver с открытым исходным кодом?!

ДА! Вы не ослышались - Portal Solver теперь open-source!
Но чтобы вы меньше тратили время на билд проекта (почти 1.5к ассетов, я не шучу 💀)

Я решил скомпилировать все за вас! Так что успейте опробовать скомпилированные исходники Портал Солвера БЕСПЛАТНО!

Ссылочка: тык
🤩3👀1
𝐑𝐨𝐨𝐭𝐓𝐨𝐨𝐥 𝐁𝐥𝐨𝐠
Искусство Построения Архитектуры Проекта Глава 4: "Квантовая запутанность" классов и "теория большого #includ'а" Вспомним: - Глава 2 - #include "Path/File.h" - открывает файл по пути и жестко заменяет себя на его содержимое - Глава 3 - Forward Declaration…
Forward Declaration на реальном примере

Как всегда работал (да.. в 2:25 ночи) и встретил распространенную/привычную проблему - в одной структуре я ссылаюсь (упоминаю) другую структуру, что находится чуть ниже

Что делать? Forward Declaration!

вот так примеры с учебника реально встречаются на работе))

Don’t Just Learn - Understand It. Think Cold
❤3👍1🔥1
Есть дьявол такой - дедлайн называется

но главное: работает - не трогай
🤯4❤2👍1👀1
Что ж, взял себе наконец-таки новый (второй) блокнот для Portal: Solver

А что с первым? А первый я взял ещё в прошлом году, когда только Альфа начиналась...

И сейчас он весь исписан расчётами, концептами камер, архитектур кода, систем/подсистем, и вообще темный лес☠️

Теперь, как говорится, начнем с чистого листа
❤2💯2🔥1
Учите плюсы, братья 💪
❤5❤‍🔥2🤓1😎1
Искусство Построения Архитектуры Проекта

Глава 6: Enumerating the world!

Enum, enum, енум... что такое этот ваш enum? По сути - более красивая замена чисел на понятные слова.
Его синтаксис прост до безобразия:
enum class EnumName : EnumType
{
A, B, C
};

Что можно сразу выделить:
- EnumName - название нашего перечисления
- EnumType - тип перечисления. По дефолту это int, но могут быть любые другие целочисленные типы (поговорим чуть позже)

Теперь можно обращаться к перечислению как: if(EnumName::A == EnumName::B);, или, EnumName MyEnumVar = EnumName::A;

Прикольно, да? Но что находится под enum? При компиляции компилятор просто идёт по элементам сверху вниз. Если значение у элемента не указано - берёт значение предыдущего и прибавляет к нему +1 (И да, по умолчанию первый элемент равен 0) То есть:
enum class EnumName : EnumType
{
// Начинаем счет с нуля
A, // 0
B, // 1
C // 2
};

Но каков предел и размерность значений? А вот именно тут ключевую роль играет EnumType (по умолчанию - int). Но можно, например, enum class EnumName : uint8_t - 8 битное целое число без знака (от 0 до 255)

Но плюсы на то плюсы, что их гибкость позволяет задавать элементам любые значения. Разберем конкретный кейс:
enum class ECode // int
{
OK = 200,
NotFound = 404,
Something // 405
};

И теперь при компиляции, код OK примет значение 200, NotFound - 404, а Something (как мы сказали выше) будет равен 405, так как компилятор даст ему значение относительно значения предыдущего элемента, да +1 (404 + 1 = 405)

А благодаря удобству enum, теперь мы можем создавать функции ECode GetCode(); и возвращать значения с помощью return ECode::OK; - удобно? Ещё как! И это одна из причин, почему enum активно используют в архитектуре

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

Enumerate The World. Think Cold.
❤4❤‍🔥1💯1
This media is not supported in your browser
VIEW IN TELEGRAM
❤3
C++ - самый, сука, константный язык

Глядите, у нас есть базовый пример:
int x = 5;
int* px = &x;

^ Указатель на x
Можно менять указатель
Можно менять значение

const int* px = &x;

^ Указатель на константное значение
Можно менять указатель
Нельзя менять значение

int* const px = &x;

^ Константный указатель на значение
Нельзя менять указатель
Можно менять значение

Что ж.. а теперь барабанная дробь:
const int* const px = &x;

^ Константный указатель на константное значение
Нельзя менять указатель
Нельзя менять значение

Но тише, тише, это только начало:
int* const* ppx = &px;

^ Указатель на константный указатель на значение
Можно менять указатель
Нельзя менять значение указателя p
Можно менять само значение x (через **ppx)

const int* const* ppx = &px;

^ Константный указатель на константный указатель на константное значение
Нельзя менять указатель
Нельзя менять значение указателя p
Нельзя менять само значение x


Ладно, хватит! Я еще не стал душнить про int* const* const** ppppx....
указатель на указатель на константный указатель на константный указатель на x

C++ штука такая. Живите с этим :)
❤4👀1
This media is not supported in your browser
VIEW IN TELEGRAM
❤1💋1
This media is not supported in your browser
VIEW IN TELEGRAM
😎2❤1
С первым днем зимы, IT-шники!

а у меня все так и не появилось время писать в блог, готовим вам что-то интересное
❤5☃1😁1🎄1
this->CelebrateBirthday();
//TODO: :tada effect

И вот.. Подходит мой 8-й год в С++ 17-й год жизни, и знаете? Я счастлив что встретил вас всех, с кем остались, а с кем, конечно, разошлись

И правда, еще 2 года назад, до начала Portal: Solver и всего этого я думал что к 16-17 годам буду как те самые подростки на вписках - алкоголь, курение и всякое подобное. Считал что моя жизнь увязнет в этом, но....

Сейчас проект, цель, будущее - и правда, это не произошло бы без всех вас.

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

Спасибо
❤5🎉3