C++11及以上创建未初始化栈内存的合规可移植方法
栈上创建未初始化对象数组的实现与标准合规性分析
你提供的实现代码
#include <iostream> #include <string> #include <array> #include <cstddef> #include <new> struct Object { std::size_t m_id = 0; std::string m_name = ""; Object() { std::cout << "I'm Alive\n"; } Object(const int id, const std::string& name) : m_id(id) , m_name(name) { std::cout << "I am " << m_name << " # " << m_id << "\n"; } Object(const Object& o) : m_id(o.m_id) , m_name(o.m_name) { std::cout << "Copied\n"; } ~Object() { std::cout << "Goodbye for now\n"; } }; void stackAllocationExample() { static constexpr std::size_t nObjects = 10; static constexpr std::size_t arraySize = nObjects * sizeof(Object); alignas(std::array<Object, nObjects>) std::array<std::byte, arraySize> objects; std::array<std::string, nObjects> names = {"O1", "O2", "O3", "O4", "O5", "O6", "O7", "O8", "O9", "O10"}; for (std::size_t i = 0; i < nObjects; ++i) { new (objects.data() + sizeof(Object) * i) Object(i+1, names[i]); } for (std::size_t i = 0; i < nObjects; ++i) { std::byte* rawBytes = &(objects[0]) + sizeof(Object) * i; Object* obj = std::launder((Object*)rawBytes); std::cout << i << ": {" << obj->m_id << ", " << obj->m_name << "}\n"; obj->~Object(); } } int main() { stackAllocationExample(); return 0; }
该实现的标准合规性与跨编译器支持
这个实现符合C++17及以后的标准,核心合规点如下:
- 用
std::array<std::byte, arraySize>作为内存缓冲区,配合alignas(std::array<Object, nObjects>)确保内存对齐满足Object数组的要求,完全符合标准中关于内存对齐的规定。 - 使用placement new在缓冲区内存上逐个构造
Object对象,这是标准中显式构造对象的合法方式。 - 构造完成后调用
std::launder获取有效指针:原缓冲区是std::byte类型数组,新构造的Object类型与原类型不同,std::launder用于告知编译器该内存位置的对象已变更,避免优化导致的未定义行为,这是C++17标准针对此类场景的正确用法。 - 最后手动调用
~Object()析构对象,符合placement new构造对象后需手动析构的要求。
跨编译器支持方面,主流编译器(GCC 7+、Clang 5+、MSVC 2017+)都完整支持C++17的这些特性,因此该实现可以在这些编译器上正常运行。
C++17引入std::launder前的实现方式
C++17之前没有std::byte和std::launder,可以通过以下方式实现栈上未初始化对象数组:
- 用
unsigned char数组代替std::byte数组作为内存缓冲区——标准允许char/unsigned char用于存储其他对象的原始字节。 - 用
alignas(Object)直接保证内存对齐到Object类型的要求。 - 指针转换时直接使用
reinterpret_cast<Object*>——C17之前针对char/unsigned char缓冲区上构造的对象,主流编译器会认可这种转换的有效性(尽管标准在此处的规定不如C17明确,但实践中是安全的)。
对应的实现代码如下:
#include <iostream> #include <string> #include <array> #include <cstddef> #include <new> struct Object { std::size_t m_id = 0; std::string m_name = ""; Object() { std::cout << "I'm Alive\n"; } Object(const int id, const std::string& name) : m_id(id) , m_name(name) { std::cout << "I am " << m_name << " # " << m_id << "\n"; } Object(const Object& o) : m_id(o.m_id) , m_name(o.m_name) { std::cout << "Copied\n"; } ~Object() { std::cout << "Goodbye for now\n"; } }; void stackAllocationExample() { static constexpr std::size_t nObjects = 10; static constexpr std::size_t arraySize = nObjects * sizeof(Object); // 用unsigned char数组作为缓冲区,alignas保证对齐 alignas(Object) unsigned char objects[arraySize]; std::array<std::string, nObjects> names = {"O1", "O2", "O3", "O4", "O5", "O6", "O7", "O8", "O9", "O10"}; for (std::size_t i = 0; i < nObjects; ++i) { new (objects + sizeof(Object) * i) Object(i+1, names[i]); } for (std::size_t i = 0; i < nObjects; ++i) { unsigned char* rawBytes = objects + sizeof(Object) * i; // C++17前直接用reinterpret_cast转换指针 Object* obj = reinterpret_cast<Object*>(rawBytes); std::cout << i << ": {" << obj->m_id << ", " << obj->m_name << "}\n"; obj->~Object(); } } int main() { stackAllocationExample(); return 0; }
内容的提问来源于stack exchange,提问作者Malphrush
相关产品推荐
相关产品推荐

