Новое размещение С++ для создания глобальных объектов с определенным порядком построения. Правильно ли это использование?

Я использую фреймворк Ардуино. Чтобы избежать проблем с динамической памятью (опустошение кучи и переполнение стека), Arduino широко работает с глобальными объектами. Я думаю, что это хорошая практика, и я хочу продолжить работу с этим шаблоном.

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

Но в С++ нет определенного порядка построения глобальных объектов.

Чтобы преодолеть это, я решил, что могу использовать новый оператор размещения и создавать объекты в памяти глобальных объектов. Я создал шаблон с единственной целью резервирования памяти для любого типа T, который я хочу создать в глобальном пространстве памяти.

Чтобы зарезервировать фактическое пространство памяти, у меня есть член buffer_, который я объявил как массив long, чтобы убедиться, что память идеально выровнена для любых объектов. Но это вызывает предупреждения о проблемах с выравниванием.

С другой стороны, использование массива char отлично работает без предупреждения. Но я думаю, что гораздо менее вероятно, что он будет правильно выровнен для любого T.

Вопрос: Почему массив символов, по-видимому, правильно выровнен, а массив длинных - нет?

В следующем коде показан шаблон бронирования, а во втором фрагменте показано, как его использовать:

#include <memory>

template<class T>
class ObjectMemory
{
    //long buffer_[(sizeof(T) + sizeof(long)-1)/sizeof(long)];//make sure it is aligned for long (=biggest size)
    //=> but this line creates warnings regarding alignments: 
    // warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
    //     T& operator *()  { return reinterpret_cast<T&>(*buffer_); }
    //                                                     ^~~~~~
    char buffer_[sizeof(T)]; //this line compiles without warning but looks to me less aligned than an array of longs.

public:
    ObjectMemory() = default;
    
    ObjectMemory( const ObjectMemory &c ) = delete;
    ObjectMemory& operator=( const ObjectMemory &c ) = delete;
    
    T& operator  *() { return reinterpret_cast<T&>(*buffer_); }
    T* operator ->() { return reinterpret_cast<T*>( buffer_); }
       operator T&() { return reinterpret_cast<T&>(*buffer_); }
    void destroyObject(){(reinterpret_cast<T*>(buffer_))->~T();}
};

template<class T>
void * operator new(size_t cbsize, ObjectMemory<T>& objectMemory)
{
    if(cbsize > sizeof(T))
    {
        while(true){}      //alternatively return nullptr; (will cause warnings)
    }
    return &objectMemory;
}

А вот использование шаблона и размещение нового оператора:

//global objects
ObjectMemory<Driver_Spi> spi;
ObjectMemory<Driver_DigitalPin> ACS_BoardAdcSpiCs;
ObjectMemory<Driver_InternalRtc> rtc;
ObjectMemory<Driver_BoardAdc> boardAdc;
ObjectMemory<Dcf77Decoder> dcf77Decoder;

// The setup() function runs once each time the micro-controller starts
void setup()
{
    //...
    // now call the constructors in correct order:

    new (spi)                   Driver_Spi();
    new (ACS_BoardAdcSpiCs)     Driver_DigitalPin(PIN_5);//CSA
    new (rtc)                   Driver_InternalRtc();
    new (boardAdc)              Driver_BoardAdc(spi, ACS_BoardAdcSpiCs);
    new (dcf77Decoder)          Dcf77Decoder(rtc, PIN_0); //DCF77_INPUT (with interrupt)
 
    //...
    boardAdc->init();
}

void loop()
{
    //...
}

Вы имеете в виду фиаско статического порядка инициализации? В одной и той же единице трансляции порядок инициализации четко определен. Возможно, взгляните на шаблон проектирования singleton.

MatG 22.04.2022 19:51

Глобальные объекты, определенные в том же исходном файле, инициализируются всегда в порядке их определения. Только когда у вас есть глобальные объекты, определенные более чем в одном исходном файле, вам нужно беспокоиться о порядке инициализации, в основном потому, что соответствующие объектные файлы могут быть связаны в разном порядке, поэтому их инициализаторы могут запускаться в разном порядке.

Pete Becker 23.04.2022 00:11
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
0
2
48
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Предупреждение, которое вы видите, не связано с выравниванием, а связано с каламбуром. Каламбур типа относится к одной и той же ячейке памяти с двумя указателями разных типов (long* и T*).

Из справочник по языку с++ есть только несколько специальных типов, для которых компилятор не мочь выдает предупреждение (char — один из этих специальных типов):

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

  • AliasedType and DynamicType are similar.
  • AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
  • AliasedType is std::byte, (since C++17) char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

Это объясняет, почему компилятор предупреждает, когда буфер вводится как long[], и не предупреждает при использовании char[].

Чтобы правильно выровнять char[] по T, вы должны использовать спецификатор alignas(). Это заставит компилятор выровнять ваш char[], как если бы это был T. Например, ваш класс ObjectMemory можно изменить следующим образом:

template<class T>
class ObjectMemory
{
    alignas(T) char buffer_[(sizeof(T)];

public:
    ObjectMemory() = default;
    
    ObjectMemory( const ObjectMemory &c ) = delete;
    ObjectMemory& operator=( const ObjectMemory &c ) = delete;
    
    T& operator  *() { return reinterpret_cast<T&>(*buffer_); }
    T* operator ->() { return reinterpret_cast<T*>( buffer_); }
       operator T&() { return reinterpret_cast<T&>(*buffer_); }
    void destroyObject(){(reinterpret_cast<T*>(buffer_))->~T();}
};

Спасибо! Это именно то, что я искал и решил проблему. Очевидно, я не знал о ключевом слове alignas. Пора мне еще раз пройтись по списку ключевых слов.

phu 29.04.2022 17:43

Другие вопросы по теме