C++ placement new 以定义构造顺序创建全局对象 - 这种用法是否正确?
C++ placement new to create global objects with defined construction order - Is this usage correct?
我正在使用 Arduino 框架。
为避免动态内存问题(堆下溢和堆栈溢出),Arduino 广泛使用全局对象。我认为这是很好的做法,我想继续使用这种模式。
同时,我想对那些全局对象使用依赖注入,即一些对象需要在其构造函数中注入其他对象。
但是在c++中构造全局对象没有定义的顺序。
为了克服这个问题,我想我可以使用放置新运算符并将对象构造到全局对象的内存中。我构建了一个模板,其唯一目的是为我想在全局内存 space.
中创建的任何类型 T
保留内存
为了保留实际内存 space,我有一个成员 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()
{
//...
}
您看到的警告与对齐无关,而是键入双关语。类型双关是指具有两个不同类型指针(long* 和 T*)的同一内存位置。
从 c++ language reference 开始,只有少数特殊类型编译器 不能 发出警告(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
class 可以修改如下:
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();}
};
我正在使用 Arduino 框架。 为避免动态内存问题(堆下溢和堆栈溢出),Arduino 广泛使用全局对象。我认为这是很好的做法,我想继续使用这种模式。
同时,我想对那些全局对象使用依赖注入,即一些对象需要在其构造函数中注入其他对象。
但是在c++中构造全局对象没有定义的顺序。
为了克服这个问题,我想我可以使用放置新运算符并将对象构造到全局对象的内存中。我构建了一个模板,其唯一目的是为我想在全局内存 space.
中创建的任何类型T
保留内存
为了保留实际内存 space,我有一个成员 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()
{
//...
}
您看到的警告与对齐无关,而是键入双关语。类型双关是指具有两个不同类型指针(long* 和 T*)的同一内存位置。
从 c++ language reference 开始,只有少数特殊类型编译器 不能 发出警告(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
class 可以修改如下:
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();}
};