Forger 20 Среда в 16:42 Опубликовано Среда в 16:42 · Жалоба 9 minutes ago, EdgeAligned said: Поскольку пишу только для себя и не предполагаю, что моя писанина вообще будет кому-то нужна Золотые слова! )) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 15 Среда в 17:12 Опубликовано Среда в 17:12 · Жалоба 56 минут назад, Arlleex сказал: Если бы была возможность дробить определение класса (как, например, namespace), и выносить интерфейсную часть в хедер, а все темные дела в .cpp, то был бы такой же эффект. Но так сделать нельзя. Ослиные уши в виде прототипов приватных функций, да и сами приватные данные, будут видны в определении класса, т.е. в хедере. Здесь можно вспомнить про идиому pimpl, которая, кроме припрятывания приватных дел в cpp-файл, теоретически позволяет уменьшить число перекомпиляций (можно много что менять в реализации без изменений в *.h-файле. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 153 Среда в 17:41 Опубликовано Среда в 17:41 · Жалоба 1 час назад, EdgeAligned сказал: Впрочем, лично я не заморачиваюсь постройкой того, на что комитет по ++ просто забил. Можно, конечно, забить. Но это не мой путь. Мой путь - создать свое видение "идеального" кода на плюсах, с его тонкостями и нюансами. И чтобы это выработать, нужно пролопатить 100500 вариантов от множества разработчиков, послушать их опыт и рекомендации. Опять же - я не по работе щас пекусь - там я просто прогаю на C++ в Си стиле - нет трехэтажных абстракций ради абстракций. А в свободное время мне интересно поковыряться с синтаксическими штуками, дабы оставить, в конечном счете, только нужное, чтобы если это написано - значит это нужно, а не потому что некий костыль надо залатать. Пока что да, костылей очень много - в частности, недавнее упрятывание дефолтного конструктора в приват и обозначение его как = delete. Но с другой стороны, из-за того, что в плюсах наблюдается тенденция реализовывать абсолютно одинаковые по замыслу вещи совсем разными подходами, становится возможным отсечь максимально ущербные варианты и оставить лучшее)) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
razrab83 21 Среда в 17:42 Опубликовано Среда в 17:42 · Жалоба 1 час назад, EdgeAligned сказал: Proteted - это область видимости в первом уровне наследования. я знаю что такое протектед и для чего он используется. Поэтому я его не использую ни для членов, ни для функций. Это источник проблем. По сути это тот же паблик, только среди ограниченного круга. Это ваш личный холодильник на этаже в общежитии/комуналке. Кроме Вас, к нему имеют доступ все жители общяги, но ни кто "чужой" не сможет туда попасть )). 1 час назад, EdgeAligned сказал: Если же писать "для всех", то в идеале должны быть закрытые библиотеки в виде файлов типа .a Если писать для всех закрытые библиотеки в виде файлов типа .a, то да. А если писать для всех срр или работать в команде.... хороший пример "для всех" всякие ротос (кернел, фриртос, сцм), Qt, Qwt, LVGL (ой, это же не всё ++), boost, .... и все с исходниками без. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 153 Среда в 17:46 Опубликовано Среда в 17:46 · Жалоба 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 20 Среда в 18:08 Опубликовано Среда в 18:08 · Жалоба 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 153 Среда в 18:24 Опубликовано Среда в 18:24 · Жалоба 12 минут назад, Forger сказал: А я бы так переделал, убрав в cpp всю реализацию и "лишние" методы: Тут я об этом думал. Но поскольку содержимое этих SendCommand() попарно одинаково с соответствующим SendData(), не очень хотелось дублировать код в реализации. Поэтому запихнул вызов в inline-функции класса. Он, вроде, должен сразу подставить вызов нужного send(), а в образе прошивки не будет 8 разных функций. Будет 4 перегрузки send(). Я тут еще подумаю, как оно будет попрактичней. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 20 Среда в 18:28 Опубликовано Среда в 18:28 · Жалоба 2 minutes ago, Arlleex said: Тут я об этом думал. Но поскольку содержимое этих SendCommand() попарно одинаково с соответствующим SendData(), не очень хотелось дублировать код в реализации. Поэтому запихнул вызов в inline-функции класса. А вот тут не стоит переживать, компиляторы нынче умные и довольно умело оптимизируют код )) Я уже давно перестал над этим заморачиваться. Реально глянул несколько раз код в ассемблере в релизной сборке и был неоднократно приятно удивлен ) 4 minutes ago, Arlleex said: Он, вроде, должен сразу подставить вызов нужного send(), а в образе прошивки не будет 8 разных функций. В образе прошивки и так не будет лишних функций, если они в коде ни разу не вызываются. Для этого только нужно комплилировать с нужным ключиком: Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 153 Среда в 18:31 Опубликовано Среда в 18:31 · Жалоба 3 минуты назад, Forger сказал: А вот тут не стоит переживать, компиляторы нынче умные и довольно умело оптимизируют код )) Я уже давно перестал над этим заморачиваться. Реально глянул несколько раз код в ассемблере в релизной сборке и был неоднократно приятно удивлен ) Ну тогда тут класс, в общем-то, не нужен. Достаточно заменить class на namespace и иметь просто именованный набор API-функций. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Forger 20 Среда в 18:32 Опубликовано Среда в 18:32 · Жалоба 2 minutes ago, Arlleex said: Достаточно заменить class на namespace и иметь просто именованный набор API-функций. Ну вообще то да, но именно в данном случае )) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 153 Среда в 19:36 Опубликовано Среда в 19:36 · Жалоба 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 73 Среда в 21:23 Опубликовано Среда в 21:23 · Жалоба 3 часа назад, razrab83 сказал: я его не использую ни для членов, ни для функций. Зря. Никаких там проблем нет, если понимать, для чего и как использовать protected Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 73 Четверг в 05:42 Опубликовано Четверг в 05:42 · Жалоба Да, а для создания подобия интерфейса можно воспользоваться наследованием чистого виртуального классса, вот так: 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 20 Четверг в 06:20 Опубликовано Четверг в 06:20 · Жалоба 37 minutes ago, EdgeAligned said: И еще одна жопа кроется в том, что все инклюды, которые вы подключили в конкретный .hpp, становятся неявно видимыми везде. Так не надо туда подключать лишние инклуды )) Поэтому в hpp кладу только те инклуды, который нужны для работы препроцессора на этот hpp файл, а все остальные инклуды - в соотв. cpp файл Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 73 Четверг в 07:57 Опубликовано Четверг в 07:57 · Жалоба Так вот ведь, вроде бы решение шикарное, но как только сталкиваетесь с шаблонами, это шикарное решение разбивается о суровую действительность недоработок языка 🙂 и не получается сделать единообразие стиля оформления - где-то так, где-то сяк, че попало выходит. вообще, я разочаровался в ++, он ущербный какой-то, мусорка из ненужного старого и недоделанного нового. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться