Forger 22 26 июня Опубликовано 26 июня · Жалоба 9 minutes ago, EdgeAligned said: Поскольку пишу только для себя и не предполагаю, что моя писанина вообще будет кому-то нужна Золотые слова! )) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 26 июня Опубликовано 26 июня · Жалоба 56 минут назад, Arlleex сказал: Если бы была возможность дробить определение класса (как, например, namespace), и выносить интерфейсную часть в хедер, а все темные дела в .cpp, то был бы такой же эффект. Но так сделать нельзя. Ослиные уши в виде прототипов приватных функций, да и сами приватные данные, будут видны в определении класса, т.е. в хедере. Здесь можно вспомнить про идиому pimpl, которая, кроме припрятывания приватных дел в cpp-файл, теоретически позволяет уменьшить число перекомпиляций (можно много что менять в реализации без изменений в *.h-файле. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 166 26 июня Опубликовано 26 июня · Жалоба 1 час назад, EdgeAligned сказал: Впрочем, лично я не заморачиваюсь постройкой того, на что комитет по ++ просто забил. Можно, конечно, забить. Но это не мой путь. Мой путь - создать свое видение "идеального" кода на плюсах, с его тонкостями и нюансами. И чтобы это выработать, нужно пролопатить 100500 вариантов от множества разработчиков, послушать их опыт и рекомендации. Опять же - я не по работе щас пекусь - там я просто прогаю на C++ в Си стиле - нет трехэтажных абстракций ради абстракций. А в свободное время мне интересно поковыряться с синтаксическими штуками, дабы оставить, в конечном счете, только нужное, чтобы если это написано - значит это нужно, а не потому что некий костыль надо залатать. Пока что да, костылей очень много - в частности, недавнее упрятывание дефолтного конструктора в приват и обозначение его как = delete. Но с другой стороны, из-за того, что в плюсах наблюдается тенденция реализовывать абсолютно одинаковые по замыслу вещи совсем разными подходами, становится возможным отсечь максимально ущербные варианты и оставить лучшее)) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
razrab83 21 26 июня Опубликовано 26 июня · Жалоба 1 час назад, EdgeAligned сказал: Proteted - это область видимости в первом уровне наследования. я знаю что такое протектед и для чего он используется. Поэтому я его не использую ни для членов, ни для функций. Это источник проблем. По сути это тот же паблик, только среди ограниченного круга. Это ваш личный холодильник на этаже в общежитии/комуналке. Кроме Вас, к нему имеют доступ все жители общяги, но ни кто "чужой" не сможет туда попасть )). 1 час назад, EdgeAligned сказал: Если же писать "для всех", то в идеале должны быть закрытые библиотеки в виде файлов типа .a Если писать для всех закрытые библиотеки в виде файлов типа .a, то да. А если писать для всех срр или работать в команде.... хороший пример "для всех" всякие ротос (кернел, фриртос, сцм), Qt, Qwt, LVGL (ой, это же не всё ++), boost, .... и все с исходниками без. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 166 26 июня Опубликовано 26 июня · Жалоба 35 минут назад, AHTOXA сказал: Здесь можно вспомнить про идиому pimpl, которая, кроме припрятывания приватных дел в cpp-файл, теоретически позволяет уменьшить число перекомпиляций (можно много что менять в реализации без изменений в *.h-файле. Ехех) Если б еще я детально понимал работу умных указателей)) Поэтому пока что оставил в заметках) P.S. Примерно в такое сейчас вырисовывается интерфейс к драйверу дисплейчика (еще пяток функций будет, но это уже не суть важно) namespace OLED { class Hardware final { public: static void init(); static bool sendCommand(u8 cmd) { return send(COMMAND, cmd); } static bool sendCommand(u8 cmd1, u8 cmd2) { return send(COMMAND, cmd1, cmd2); } static bool sendCommand(u8 cmd1, u8 cmd2, u8 cmd3) { return send(COMMAND, cmd1, cmd2, cmd3); } static bool sendCommand(u8 const cmdBuf[], u32 size) { return send(COMMAND, cmdBuf, size); } static bool sendData(u8 data) { return send(DATA, data); } static bool sendData(u8 data1, u8 data2) { return send(DATA, data1, data2); } static bool sendData(u8 data1, u8 data2, u8 data3) { return send(DATA, data1, data2, data3); } static bool sendData(u8 const dataBuf[], u32 size) { return send(DATA, dataBuf, size); } private: enum TransferType : bool { COMMAND = 0, DATA }; Hardware() = delete; static bool send(TransferType dc, u8 arg); static bool send(TransferType dc, u8 arg1, u8 arg2); static bool send(TransferType dc, u8 arg1, u8 arg2, u8 arg3); static bool send(TransferType dc, u8 const argBuf[], u32 size); }; } Сразу видно, что есть функция init(), которую клиентский код вызовет для инициализации железа. Также видны (в публичной интерфейсной части) inline-функции транзакций, с различными форматами. В приват запихнуты вещи, которые "не хотелось бы" отдавать в API напрямую. Сначала всякие энумы, потом уже функции. Приватные функции реализуются уже в *.cpp, поэтому пока что соблюдается идеология скрытия реализации подальше от глаз и потенциального изменения чего-либо при раздельной компиляции в статическую либу. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 26 июня Опубликовано 26 июня · Жалоба 19 minutes ago, Arlleex said: римерно в такое сейчас вырисовывается интерфейс к драйверу дисплейчика (еще пяток функций будет, но это уже не суть важно) А я бы так переделал, убрав в cpp всю реализацию и "лишние" методы: namespace OLED { class Hardware final { Hardware() = delete; public: static void init(); static bool sendCommand(u8 cmd); static bool sendCommand(u8 cmd1, u8 cmd2); static bool sendCommand(u8 cmd1, u8 cmd2, u8 cmd3); static bool sendCommand(u8 const cmdBuf[], u32 size); static bool sendData(u8 data); static bool sendData(u8 data1, u8 data2); static bool sendData(u8 data1, u8 data2, u8 data3); static bool sendData(u8 const dataBuf[], u32 size); }; } Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 166 26 июня Опубликовано 26 июня · Жалоба 12 минут назад, Forger сказал: А я бы так переделал, убрав в cpp всю реализацию и "лишние" методы: Тут я об этом думал. Но поскольку содержимое этих SendCommand() попарно одинаково с соответствующим SendData(), не очень хотелось дублировать код в реализации. Поэтому запихнул вызов в inline-функции класса. Он, вроде, должен сразу подставить вызов нужного send(), а в образе прошивки не будет 8 разных функций. Будет 4 перегрузки send(). Я тут еще подумаю, как оно будет попрактичней. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 26 июня Опубликовано 26 июня · Жалоба 2 minutes ago, Arlleex said: Тут я об этом думал. Но поскольку содержимое этих SendCommand() попарно одинаково с соответствующим SendData(), не очень хотелось дублировать код в реализации. Поэтому запихнул вызов в inline-функции класса. А вот тут не стоит переживать, компиляторы нынче умные и довольно умело оптимизируют код )) Я уже давно перестал над этим заморачиваться. Реально глянул несколько раз код в ассемблере в релизной сборке и был неоднократно приятно удивлен ) 4 minutes ago, Arlleex said: Он, вроде, должен сразу подставить вызов нужного send(), а в образе прошивки не будет 8 разных функций. В образе прошивки и так не будет лишних функций, если они в коде ни разу не вызываются. Для этого только нужно комплилировать с нужным ключиком: Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 166 26 июня Опубликовано 26 июня · Жалоба 3 минуты назад, Forger сказал: А вот тут не стоит переживать, компиляторы нынче умные и довольно умело оптимизируют код )) Я уже давно перестал над этим заморачиваться. Реально глянул несколько раз код в ассемблере в релизной сборке и был неоднократно приятно удивлен ) Ну тогда тут класс, в общем-то, не нужен. Достаточно заменить class на namespace и иметь просто именованный набор API-функций. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 26 июня Опубликовано 26 июня · Жалоба 2 minutes ago, Arlleex said: Достаточно заменить class на namespace и иметь просто именованный набор API-функций. Ну вообще то да, но именно в данном случае )) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 166 26 июня Опубликовано 26 июня · Жалоба enum Command { SPI_SEND_FLASH, SPI_SEND_RAM, SPI_SEND, SPI_FILL, SET_PIN, RESET_PIN, ... }; template<Command> struct Descriptor; template<> struct Descriptor<SPI_SEND_FLASH> { Command cmd : 3; bool dc : 1; u32 addr : 19, size : 9; } __attribute__((aligned(4))); template<> struct Descriptor<SPI_SEND_RAM> { Command cmd : 3; bool dc : 1; u32 addr : 19, size : 9; } __attribute__((aligned(4))); template<> struct Descriptor<SPI_SEND> { Command cmd : 3; bool dc : 1; u32 size : 2; u8 byte[3]; } __attribute__((aligned(4))); template<> struct Descriptor<SPI_FILL> { Command cmd : 3; bool dc : 1; u32 size : 10; u8 byte; } __attribute__((aligned(4))); template<> struct Descriptor<SET_PIN> { Command cmd : 3; Pin pin : 1; } __attribute__((aligned(4))); template<> struct Descriptor<RESET_PIN> { Command cmd : 3; Pin pin : 1; } __attribute__((aligned(4))); ... Вот есть у меня шаблон структурок, которые уже правильно "упакованы" по битам. Идея в том, что все эти структуры занимают в памяти ровно 4 байта (1 слово), и мой "планировщик" заданий работает с такими вот дескрипторами. В конечном счете, потом будет создан пул в памяти из таких вот дескрипторов, которые будут вычитываться драйвером и отдаваться на исполнение в зависимости от содержимого поля cmd. А клиентский код пишет в очередь заданий и не парится о готовности их завершения. Этакий драйвер с асинхронным неблокирующим API. Не суть, просто чтобы было понимание. У всех без исключения специализаций структур первым полем идет поле команды, оно занимает 3 бита. Я хотел бы, чтобы при объявлении конкретного объекта, например, Descriptor<SPI_SEND> desc; в его поле cmd уже "автоматически" неявно попадало число SPI_SEND из перечислителя. Полагаю, что никаких вариантов, кроме явного определения абсолютно одинаковых конструкторов в каждой из специализаций - нет? Думал, что в "основном" шаблоне можно было бы описать "единый" конструктор по-умолчанию, но это приводит к тому, что элемент cmd нужно перетаскивать в тело шаблона структуры, а специализации заменить на наследование. Но отделяя/забирая cmd из других структур, теряется гарантия, что все эти структуры останутся равны 4 байтам, т.к. при наследовании классов их члены будут иметь раздельно адресуемое положение. Битовое поле не адресуется, поэтому под cmd в шаблоне выделился бы какой-то объем отдельных байтов, что не годится. Всем структурам по конструктору, получается? Или можно, все-таки, каким-то образом сказать компилятору, чтобы представлял содержимое потомков как "синтаксическое продолжение" к данным класса-родителя? Т.е. чтобы записать template<Command> struct DescriptorHeader { Command cmd : 3; }; struct Descriptor_SPI_SEND : public DescriptorHeader { bool dc : 1; u32 addr : 19, size : 9; } __attribute__((aligned(4))); и компилятор разместил cmd в первых трех битах машинного слова, в котором будет храниться объект типа Descriptor_SPI_SEND. P.S. Посморел что сгенерировал компилятор - хмм... он при наследовании действительно "прилепил" cmd к тому же слову, что и остальные поля дочернего класса. Это хорошо, но... надежно ли? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 79 26 июня Опубликовано 26 июня · Жалоба 3 часа назад, razrab83 сказал: я его не использую ни для членов, ни для функций. Зря. Никаких там проблем нет, если понимать, для чего и как использовать protected Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 79 27 июня Опубликовано 27 июня · Жалоба Да, а для создания подобия интерфейса можно воспользоваться наследованием чистого виртуального классса, вот так: class A_Interface { public: virtual void Foo1() = 0; virtual void Foo2(int x) = 0; }; class A: public A_Interface { public: void Foo1() override { } void Foo2(int x) override { } private: int a, b, c; void Priv() { } }; Однако, и тут есть бочка дёгтя в ложке меда: статические методы не могут быть виртуальными и переопределены при наследовании. Именно поэтому я в принципе то не заморачиваюсь. Если код реализации объемный, я выношу его из описания класса, а в классе оставляю только определения. И еще одна жопа кроется в том, что все инклюды, которые вы подключили в конкретный .hpp, становятся неявно видимыми везде. Тоже неустраненное наследство от Си, на которое все забили. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 22 27 июня Опубликовано 27 июня · Жалоба 37 minutes ago, EdgeAligned said: И еще одна жопа кроется в том, что все инклюды, которые вы подключили в конкретный .hpp, становятся неявно видимыми везде. Так не надо туда подключать лишние инклуды )) Поэтому в hpp кладу только те инклуды, который нужны для работы препроцессора на этот hpp файл, а все остальные инклуды - в соотв. cpp файл Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 79 27 июня Опубликовано 27 июня · Жалоба Так вот ведь, вроде бы решение шикарное, но как только сталкиваетесь с шаблонами, это шикарное решение разбивается о суровую действительность недоработок языка 🙂 и не получается сделать единообразие стиля оформления - где-то так, где-то сяк, че попало выходит. вообще, я разочаровался в ++, он ущербный какой-то, мусорка из ненужного старого и недоделанного нового. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться