Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
Re: Статический класс или namespace для singleton?
Здравствуйте, cppguard, Вы писали:
C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
Пиши обычный класс и не компостируй мозг. А синглетон он или статическая переменная, один он будет или десять это не его зона ответственности. Какие ему диапазоны выдали при создании пусть с ними и работает.
struct Serial {
static Serial* create(const char *config);
virtual int read(void* data,int size)=0;
virtual int write(const void* data,int size)=0;
virtual ~Serial(){}
};
...
void usage(Board *board) {
Serial *serial=board->getMainSerialPort();
serial->write("hello\n",6);
}
Re[2]: Статический класс или namespace для singleton?
Здравствуйте, kov_serg, Вы писали:
_>Пиши обычный класс и не компостируй мозг.
С таким подходом, как минимум, одна проблема — в конструкторе происходит неявная инициализация. Если об этом не знать, то можно дважды инициализировать устройство. Либо везде передавать ссылку на Serial, что тоже неудобно. А чем плох вариант с namespace? Состояние хранится в двух переменных — rx_buffer, tx_buffer. Они делаются статическими и перестают быть видимыми вне модуля. Инициализировать их не надо.
Re[3]: Статический класс или namespace для singleton?
C>С таким подходом, как минимум, одна проблема — в конструкторе происходит неявная инициализация.
С чего бы это? Вас никто не заставляет пользоваться конструктором для инициализации, это можно делать отдельными методами.
C>Если об этом не знать, то можно дважды инициализировать устройство. Либо везде передавать ссылку на Serial, что тоже неудобно. А чем плох вариант с namespace? Состояние хранится в двух переменных — rx_buffer, tx_buffer. Они делаются статическими и перестают быть видимыми вне модуля. Инициализировать их не надо.
Зачем везде передавать ссылку на Serial просто пишите в классе MyMegaDevice и this будет неявно с вами
Здравствуйте, cppguard, Вы писали:
C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
Подход с объектами проще, если потом надо будет подменить работу с одним устройством на работу с другим аналогичным устройством, а в остальном нет большой разницы при условии, что с устройством работают из одной нитки.
Здравствуйте, cppguard, Вы писали:
C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
Re: Статический класс или namespace для singleton?
Здравствуйте, cppguard, Вы писали:
C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
Порядок инициализации глобальных переменных (переменная в неймспейсе тоже глобальная) может быть любым, когда они размещены по разным единицам трансляции (cpp, ixx файлам).
Чтобы задать конкретный порядок эти штуки можно объединить как свойства одной структуры, и сделать глобальную переменную этой всей структуры.
То бишь по шагам: Я бы сделал по обычному классу на каждое устройство (с нестатическими свойствами). И те устройства, которые обращаются к друг другу положить в общую структуру, сделав её объект глобальным.
Понятно, что классы можно разнести по разным файлам, тут это не важно.
Девайсы, конструктор которых обращается к другим девайсам ставятся в этой структуре после тех, от которых они зависят.
Если же сделать классический синглтон майерса (статическая переменная внутри функции), то компилятор может (или даже обязан) обернуть доступ к ней в guard для потоков.
Конструкторы глобальных объектов вызываются до main()
Если инициализация зависит от параметров main(), то видимо придётся делать локальную переменную app, а не глобальную.
В функции, работающие с каким-то набором устройств придётся передавать их в качестве параметра (что не так удобно, как с глобальной)
А у методов класса app есть this
C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
Все зависит, конечно, от логики. Но обычно работа с устройствами ведется по некому стандартному алгоритму: открыть, почитать, пописать в устройство, проверить состояние и так в течение какого-то времени, потом почистить за собой и закрыть устройство. То есть логично использовать класс. И это подход, который идеологически верный в плюсах. Тут уже и время жизни объекта будет под твоим контролем.
Если это прям точно статические функции по своей сути, то скорее стоит сделать так же, как уже принято в проекте.
Если никак не принято (новый код), то я бы выбрал на основе уровня абстракции девайсов в твоем случае. То есть у нас уровни абстракции спускаются вниз так:
библиотека
|
V
модуль (C++20+)
|
V
неймспейс
|
V
класс
Вот на каком уровне абстракции ты видишь свои девайсы, то и используй.
Патриот здравого смысла
Re: Статический класс или namespace для singleton?
Здравствуйте, cppguard, Вы писали:
C>Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно.
Если бы каждый раз, когда я это слышал, я получал доллар... В Direct3D мохнатой версии (не знаю, как сейчас) корневой интерфейс возвращался из глобальной функции, а не через CoCreateInstance(), потому, что — а вы видели ценник на видеоускорители? Только Рокфеллеры могут купить сразу два и запускать на них разные задачи! И вообще, как вставить в одну мамку два видеоускорителя, когда там только один AGP!
Как по мне, усилия, направленные на синглтонность, всегда лучше направить на написание энумерации, которая бы возвращала единственный вариант.
Здравствуйте, Alekzander, Вы писали:
A>написание энумерации, которая бы возвращала единственный вариант.
Это что-то на кроваво-энтерпрайзном?
Дело в том, что код пишется под конкретный микроконтроллере, где есть только одна физическая шина I2C. Я прекрасно осведомлён про тяжёлое наследие прошлого. Более того, ваш пример переусложнён, можно было вспомнить rand(), переменную "err" в Си и всё остальное, что с пришествием многопоточности пришлось переписывать. Но одно не отменяет другое.
Re[3]: Статический класс или namespace для singleton?
Здравствуйте, cppguard, Вы писали:
C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические.
OMG! А статический класс с нормальными членами чем не годится?
Помимо прочего, глобальные (статические) переменые -- неудобны для кодогенератора, порождают
массу кода. Потому, что относительно this компилятору легко за одну инструкцию обычно достать
что угодно. А для глобальной переменной каждый раз начинается вычисление с адреса GOT, смещения
в нём, там достаётся адрес переменной, и потом она читается-пишется. А со следующей переменной
всё то же самое с начала.
C> Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов,
Порядок их конструирования как бы не определён. Если нужен определённый -- как вариант делать
синглтоны Мейерса. Но не панацея, т.к. возможны сценарии, когда в конструкторе класса A класс Б
не был востребован, зато востребован в деструкторе. А он уже ликвидирован (в момент выгрузки
программы или библиотеки), т.к. деструкторы для синглтонов отработают в обратном порядке, и Б
конструировался после А.
Лучше иметь чёткую иерархию классов, кто кем владеет. Собственно тогда ни глобальные переменные
(статические) не нужны, ни проблем со сроками жизни.
C> а ещё и может быть сгенерирован код, защищающий от условия гонок.
Это никак с ООП не пересекается. Единственное что, в момент инстанцирования синглтона
компилятор добавляет подпольно мьютекс, что гарантирует что синглтон сконструируется один
раз даже если востребован одновременно из разных потоков.
C> Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
См. выше. В целом подход с ООП (классами) более универсален и позволяет упорядочить
конструирование отдельных членов класса. С неймспейсом будет бардак в C-стиле. Неймспейс
скорей нужен для объединения мало связанных сущностей в общее пространство в котором используются
общие именованные малосвязанные между собой сущности (есть внешний потребитель, кому это нужно)
и в котором работает ADL (argument dependent lookup). Если у сущностей нет внешнего пользователя,
то проще их закрыть в отдельный класс, если только они нужны самому классу и никому снаружи.
Вообще язык программирования C нужно выдавливать из себя и вообще забыть. Нормальных практик
программирования там нет, одни только ненормальные.
Re[3]: Статический класс или namespace для singleton?
Здравствуйте, cppguard, Вы писали:
C>Здравствуйте, kov_serg, Вы писали:
_>>Пиши обычный класс и не компостируй мозг. C>С таким подходом, как минимум, одна проблема — в конструкторе происходит неявная инициализация.
В конструкторе как раз -- явная. Вот ты вызвал конструктор, понимаешь же что создаётся новая сущность такого-то типа.
Есть конечно нюанс с автопреобразованием типов, но можешь вписать explicit.
А все варианты с синглтонами и т.п. -- это как раз НЕявная инициализация, которая произойдёт непонятно когда.
И самое главное непонятно когда будет ДЕинициализация на выходе.
C> Если об этом не знать, то можно дважды инициализировать устройство. Либо везде передавать ссылку на Serial, что тоже неудобно.
Для этого надо разделить логику управления сроком жизни и логику самого класса. Сам класс написать обычным образом
без синглтонов и т.п. И отдельно написать что-то вроде:
Либо кто-то должен заранее сконструировать твой класс и раздавать всем остальным его инстанс. Либо его передавать
везде сверху-вниз в явном виде (ссылку). Можно сделать ресурс менеджер к которому обращаться за получением ссылки
на инстанс твоего класса, и который будет содержать в себе все подобные классы и инстанцировать их в каком-то
предопределённом порядке.
C> А чем плох вариант с namespace? Состояние хранится в двух переменных — rx_buffer, tx_buffer. Они делаются статическими и перестают быть видимыми вне модуля. Инициализировать их не надо.
Тем, что здесь кончается ООП и начинается типичный C-подобный макаронный монстр с кучей goto.
Про static в C++ можно вообще забыть, для этого есть анонимные неймспейсы. Использовать static
имеет смысл в каких-то маргинальных случаях вроде того, что static может перекрыть глобальную
функцию без ошибок, а в случае функции из неймспейса будет ошибка, мол неоднозначность
(есть выбор между {}::func и ::func).
Такой код, который не может быть свален в единый C++-файл и после этого компилироваться нормально --
не стоит вообще писать. Понятие "единицы компиляции" лучше забыть. Потому, что практически это вводит
ограничения совершенно не нужные и которые в будущем могут стать препятствием. Например появятся
юнит-тесты и там синглтон или статические переменные -- проблема. Их невозможно многократно тестировать
без перезапуска процесса.
Глобальная переменная -- это каждый раз проблема. Лучше иметь иерархию, где всё зависит от некого
начального класса и есть чёткое представление кто чем владеет и какие у чего сроки жизни.