Статический класс или namespace для singleton?
От: cppguard  
Дата: 20.07.23 21:31
Оценка:
Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?
Re: Статический класс или namespace для singleton?
От: kov_serg Россия  
Дата: 20.07.23 22:57
Оценка: 2 (2)
Здравствуйте, 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?
От: cppguard  
Дата: 20.07.23 23:05
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Пиши обычный класс и не компостируй мозг.

С таким подходом, как минимум, одна проблема — в конструкторе происходит неявная инициализация. Если об этом не знать, то можно дважды инициализировать устройство. Либо везде передавать ссылку на Serial, что тоже неудобно. А чем плох вариант с namespace? Состояние хранится в двух переменных — rx_buffer, tx_buffer. Они делаются статическими и перестают быть видимыми вне модуля. Инициализировать их не надо.
Re[3]: Статический класс или namespace для singleton?
От: kov_serg Россия  
Дата: 20.07.23 23:36
Оценка: 3 (1)
Здравствуйте, cppguard, Вы писали:


C>С таким подходом, как минимум, одна проблема — в конструкторе происходит неявная инициализация.

С чего бы это? Вас никто не заставляет пользоваться конструктором для инициализации, это можно делать отдельными методами.
struct SerialImpl : Serial {
  enum { buf_size=16 };
  char tx_buf[buf_size], rx_buf[buf_size];
  ...
} serial[2];
...
void Board::config() {
    serial[0].config(portA_config);
    serial[1].config(portB_config);
    ...
}
Serial* Board::getMainSerial() { return &serial[0]; }
Serial* Board::getAuxSerial() { return &serial[1]; }


C>Если об этом не знать, то можно дважды инициализировать устройство. Либо везде передавать ссылку на Serial, что тоже неудобно. А чем плох вариант с namespace? Состояние хранится в двух переменных — rx_buffer, tx_buffer. Они делаются статическими и перестают быть видимыми вне модуля. Инициализировать их не надо.


Зачем везде передавать ссылку на Serial просто пишите в классе MyMegaDevice и this будет неявно с вами
struct MyMegaDevice {
    Board board[1];
    void fn();
    ...
};
...
void MyMegaDevice::fn() {
    Serial *serial=board->getMainSerial();
    ...
}
Отредактировано 20.07.2023 23:38 kov_serg . Предыдущая версия .
Re: Статический класс или namespace для singleton?
От: B0FEE664  
Дата: 21.07.23 12:47
Оценка: 8 (1)
Здравствуйте, cppguard, Вы писали:

C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?


Подход с объектами проще, если потом надо будет подменить работу с одним устройством на работу с другим аналогичным устройством, а в остальном нет большой разницы при условии, что с устройством работают из одной нитки.
И каждый день — без права на ошибку...
Re: Вспомнилась картинка
От: SaZ  
Дата: 21.07.23 20:22
Оценка: :))) :))) :)))
Здравствуйте, cppguard, Вы писали:

C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?


Re: Статический класс или namespace для singleton?
От: Sm0ke Россия ksi
Дата: 22.07.23 12:35
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?


Порядок инициализации глобальных переменных (переменная в неймспейсе тоже глобальная) может быть любым, когда они размещены по разным единицам трансляции (cpp, ixx файлам).
Чтобы задать конкретный порядок эти штуки можно объединить как свойства одной структуры, и сделать глобальную переменную этой всей структуры.
То бишь по шагам: Я бы сделал по обычному классу на каждое устройство (с нестатическими свойствами). И те устройства, которые обращаются к друг другу положить в общую структуру, сделав её объект глобальным.

namespace lib_device {

struct video_device { /* ... */ };
struct audio_device { /* ... */ };

struct app {
  video_device m_video;
  audio_device m_audio;
};

app g_app;

} // ns


Понятно, что классы можно разнести по разным файлам, тут это не важно.
Девайсы, конструктор которых обращается к другим девайсам ставятся в этой структуре после тех, от которых они зависят.

Если же сделать классический синглтон майерса (статическая переменная внутри функции), то компилятор может (или даже обязан) обернуть доступ к ней в guard для потоков.
Отредактировано 22.07.2023 12:58 Sm0ke . Предыдущая версия .
Re[2]: Статический класс или namespace для singleton?
От: Sm0ke Россия ksi
Дата: 22.07.23 12:57
Оценка:
Конструкторы глобальных объектов вызываются до main()
Если инициализация зависит от параметров main(), то видимо придётся делать локальную переменную app, а не глобальную.
В функции, работающие с каким-то набором устройств придётся передавать их в качестве параметра (что не так удобно, как с глобальной)
А у методов класса app есть this

namespace lib_device {

struct video_device { /* ... */ };
struct audio_device { /* ... */ };

struct app {
  video_device m_video;
  audio_device m_audio;
};

} // ns

int main() {
  lib_device::app v_app;
}
Re: Статический класс или namespace для singleton?
От: DiPaolo Россия  
Дата: 22.07.23 13:16
Оценка:
C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические. Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов, а ещё и может быть сгенерирован код, защищающий от условия гонок. Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?

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

Если это прям точно статические функции по своей сути, то скорее стоит сделать так же, как уже принято в проекте.

Если никак не принято (новый код), то я бы выбрал на основе уровня абстракции девайсов в твоем случае. То есть у нас уровни абстракции спускаются вниз так:

библиотека
    |
    V
модуль (C++20+)
    |
    V
неймспейс
    |
    V
  класс

Вот на каком уровне абстракции ты видишь свои девайсы, то и используй.
Патриот здравого смысла
Re: Статический класс или namespace для singleton?
От: Alekzander  
Дата: 24.09.23 20:11
Оценка: +1
Здравствуйте, cppguard, Вы писали:

C>Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно.


Если бы каждый раз, когда я это слышал, я получал доллар... В Direct3D мохнатой версии (не знаю, как сейчас) корневой интерфейс возвращался из глобальной функции, а не через CoCreateInstance(), потому, что — а вы видели ценник на видеоускорители? Только Рокфеллеры могут купить сразу два и запускать на них разные задачи! И вообще, как вставить в одну мамку два видеоускорителя, когда там только один AGP!

Как по мне, усилия, направленные на синглтонность, всегда лучше направить на написание энумерации, которая бы возвращала единственный вариант.
Отредактировано 24.09.2023 20:17 Alekzander . Предыдущая версия .
Re[2]: Статический класс или namespace для singleton?
От: cppguard  
Дата: 26.09.23 01:32
Оценка:
Здравствуйте, Alekzander, Вы писали:

A>написание энумерации, которая бы возвращала единственный вариант.

Это что-то на кроваво-энтерпрайзном?

Дело в том, что код пишется под конкретный микроконтроллере, где есть только одна физическая шина I2C. Я прекрасно осведомлён про тяжёлое наследие прошлого. Более того, ваш пример переусложнён, можно было вспомнить rand(), переменную "err" в Си и всё остальное, что с пришествием многопоточности пришлось переписывать. Но одно не отменяет другое.
Re[3]: Статический класс или namespace для singleton?
От: Alekzander  
Дата: 26.09.23 06:32
Оценка:
Здравствуйте, cppguard, Вы писали:

A>>написание энумерации, которая бы возвращала единственный вариант.

C>Это что-то на кроваво-энтерпрайзном?

Это на виндово-эйпиайном.
Re: Статический класс или namespace для singleton?
От: fk0 Россия https://fk0.name
Дата: 28.09.23 07:02
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Нужно написать несколько классов, инкапсулирующих работу с аппаратной частью. Каждый класс обращается к своему устройству, поэтому переиспользования не будет совершенно точно. Первая мысль — класс, где все члены статические.


OMG! А статический класс с нормальными членами чем не годится?

Помимо прочего, глобальные (статические) переменые -- неудобны для кодогенератора, порождают
массу кода. Потому, что относительно this компилятору легко за одну инструкцию обычно достать
что угодно. А для глобальной переменной каждый раз начинается вычисление с адреса GOT, смещения
в нём, там достаётся адрес переменной, и потом она читается-пишется. А со следующей переменной
всё то же самое с начала.


C> Но потом я вспомнил, что в С++ какие-то сложные правила времени жизни статических объектов,


Порядок их конструирования как бы не определён. Если нужен определённый -- как вариант делать
синглтоны Мейерса. Но не панацея, т.к. возможны сценарии, когда в конструкторе класса A класс Б
не был востребован, зато востребован в деструкторе. А он уже ликвидирован (в момент выгрузки
программы или библиотеки), т.к. деструкторы для синглтонов отработают в обратном порядке, и Б
конструировался после А.

Лучше иметь чёткую иерархию классов, кто кем владеет. Собственно тогда ни глобальные переменные
(статические) не нужны, ни проблем со сроками жизни.


C> а ещё и может быть сгенерирован код, защищающий от условия гонок.


Это никак с ООП не пересекается. Единственное что, в момент инстанцирования синглтона
компилятор добавляет подпольно мьютекс, что гарантирует что синглтон сконструируется один
раз даже если востребован одновременно из разных потоков.

C> Поэтому вторая мысль — Си-подобный код, спрятанный в namespace. Есть у второго подхода минусы перед первым?


См. выше. В целом подход с ООП (классами) более универсален и позволяет упорядочить
конструирование отдельных членов класса. С неймспейсом будет бардак в C-стиле. Неймспейс
скорей нужен для объединения мало связанных сущностей в общее пространство в котором используются
общие именованные малосвязанные между собой сущности (есть внешний потребитель, кому это нужно)
и в котором работает ADL (argument dependent lookup). Если у сущностей нет внешнего пользователя,
то проще их закрыть в отдельный класс, если только они нужны самому классу и никому снаружи.

Вообще язык программирования C нужно выдавливать из себя и вообще забыть. Нормальных практик
программирования там нет, одни только ненормальные.
Re[3]: Статический класс или namespace для singleton?
От: fk0 Россия https://fk0.name
Дата: 28.09.23 07:21
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Здравствуйте, kov_serg, Вы писали:


_>>Пиши обычный класс и не компостируй мозг.

C>С таким подходом, как минимум, одна проблема — в конструкторе происходит неявная инициализация.

В конструкторе как раз -- явная. Вот ты вызвал конструктор, понимаешь же что создаётся новая сущность такого-то типа.
Есть конечно нюанс с автопреобразованием типов, но можешь вписать explicit.

А все варианты с синглтонами и т.п. -- это как раз НЕявная инициализация, которая произойдёт непонятно когда.
И самое главное непонятно когда будет ДЕинициализация на выходе.

C> Если об этом не знать, то можно дважды инициализировать устройство. Либо везде передавать ссылку на Serial, что тоже неудобно.


Для этого надо разделить логику управления сроком жизни и логику самого класса. Сам класс написать обычным образом
без синглтонов и т.п. И отдельно написать что-то вроде:

template <typename T>
struct Singleton
{
    T& get()
    {
        static T instance;
        return instance;
    }
};

void f()
{
    Singleton<ClassType>::get().do_operation(...);
}


Либо кто-то должен заранее сконструировать твой класс и раздавать всем остальным его инстанс. Либо его передавать
везде сверху-вниз в явном виде (ссылку). Можно сделать ресурс менеджер к которому обращаться за получением ссылки
на инстанс твоего класса, и который будет содержать в себе все подобные классы и инстанцировать их в каком-то
предопределённом порядке.


C> А чем плох вариант с namespace? Состояние хранится в двух переменных — rx_buffer, tx_buffer. Они делаются статическими и перестают быть видимыми вне модуля. Инициализировать их не надо.


Тем, что здесь кончается ООП и начинается типичный C-подобный макаронный монстр с кучей goto.

Про static в C++ можно вообще забыть, для этого есть анонимные неймспейсы. Использовать static
имеет смысл в каких-то маргинальных случаях вроде того, что static может перекрыть глобальную
функцию без ошибок, а в случае функции из неймспейса будет ошибка, мол неоднозначность
(есть выбор между {}::func и ::func).

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

Глобальная переменная -- это каждый раз проблема. Лучше иметь иерархию, где всё зависит от некого
начального класса и есть чёткое представление кто чем владеет и какие у чего сроки жизни.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.