c++放置new以创建具有定义的构造顺序的全局对象-这种用法正确吗?



我正在使用Arduino框架。为了避免动态内存的问题(堆下溢和堆栈溢出),Arduino广泛地与全局对象一起工作。我认为这是一个很好的实践,我想继续使用这种模式。

同时,我想对那些全局对象使用依赖注入,也就是说,一些对象需要在其构造函数中注入其他对象。

但是在c++中构造全局对象没有定义顺序。

为了克服这个问题,我认为可以使用放置new操作符并将对象构造到全局对象的内存中。我构造了一个模板,唯一的目的是为我想在全局内存空间中创建的任何类型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;
}
下面是模板和放置new操作符的用法:
//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()
{
//...
}

您看到的警告与对齐无关,而是与类型双关语有关。类型双关是指用两个不同类型的指针(long*和T*)指向相同的内存位置。

在c++语言参考中,只有少数特殊类型的编译器不能发出警告(char就是其中之一):

当试图通过AliasedType类型的glvalue读取或修改DynamicType类型对象的存储值时,除非满足下列条件之一,否则该行为是未定义的:

  • AliasedType和DynamicType是相似的
  • AliasedType是DynamicType的有符号或无符号变体(可能是cv限定的)。
  • AliasedType是std::byte, (c++ 17起)char,或unsigned char:这允许检查任何对象的对象表示为字节数组。

这解释了为什么编译器在缓冲区类型为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();}
};

最新更新