全局c++对象初始化
Global C++ object initialization
许多c++程序员都饱受全局c++对象初始化/清除的激烈冲突之苦。最终,我找到了一个足够好的解决这个问题的方法,我已经使用(并享受)它很多年了。我的问题是:这个解决方案是否完全符合c++标准,或者它是"依赖于平台/实现的"?
一般来说,全局对象有两个主要问题:
- 不可预测的建造/破坏顺序。如果这些对象相互依赖,这就会产生问题。
- 构造/销毁代码在主程序入口点之外的CRT初始化/清理期间执行。没有办法用
try
/catch
包装这个代码,或者执行任何初步的初始化。
main
)中的自动变量,它们的指针存储在这些指针中。这样,你就可以完全控制你的"全局"对象的生命周期。
然而,这种方法也有一些缺点。这与这样一个事实有关:不仅这些对象的创建/销毁不同,而且它们的访问也不同。通常,全局对象驻留在由加载程序分配的数据节中,并且在构建时知道它的虚拟地址。使用全局指针会导致以下缺点:
- 稍微慢一些的对象访问,额外的指针解引用。在运行时,编译器不会假设对象位于指定的地址,而是生成解引用全局指针的代码。
- 较弱的优化。编译器可能没有意识到指针总是指向同一个对象。
- 如果实际对象是在堆上分配的:
- 更差的性能(堆分配是"重")
- 内存碎片
- 内存不足异常的概率
- 如果实际对象是在堆栈上分配的(
main
中的自变量): - 堆栈大小通常是有限的。在某些情况下,"胖"对象对它的消耗是次优的。
我找到的解决方案是覆盖对象的new
/delete
操作。
// class definition
class MyObject
{
// some members
// ...
static char s_pMyPlaceholder[];
public:
// methods
// ...
static MyObject& Instance()
{
return *(MyObject*) s_pMyPlaceholder;
}
void* operator new (size_t) { return s_pMyPlaceholder; }
void operator delete (void*) {}
};
// object placeholder instantiated
char MyObject::s_pMyPlaceholder[sizeof(MyObject)];
void main()
{
// global initialization
std::auto_ptr<MyObject> pMyObj(new MyObject);
// run the program
// ...
}
诀窍是在全局内存中分配足够的空间(通过声明一个足够大小的全局数组),然后为所需对象使用有效的内存分配,这将"分配"这个全局内存。这样我们可以达到以下目的:
- 在语义上,我们动态地分配对象。因此,我们可以完全控制它的生命周期。
- 实际上对象驻留在全局内存中。因此,所有与"指针式"方法相关的缺点都不适用于我们的情况。
- 对象在程序中的任何地方都可见。调用
MyObject::Instance()
来获取对它的引用。而且,顺便说一句,这个函数调用很容易被编译器内联。
所以这个方法看起来很好。我只是好奇,从c++标准的角度来看,这是否合法。
我看到了两个问题,一个是合法性问题,一个是可用性问题。
第一个问题是对齐:MyObject::s_pMyPlaceholder
不能保证适当地对齐以容纳MyObject
。
第二个问题是您将自己限制为MyObject
类型的单个对象。创建了第二个,你已经覆盖了第一个,没有警告。
我建议使用boost::optional
延迟对象的初始化。
我不认为你有一个正式的保证,你的解决方案适用于每一个兼容的实现,因为c++标准并不能保证静态分配的char数组是对齐的,这将是任何相同大小的对象所需要的。
从3.7.3.1(分配函数,[basic.stc.dynamic.allocation]) (ISO/IEC 14882/2003):
2/[…返回的指针应适当对齐,以便它可以转换为任何完整对象类型的指针,然后用于访问已分配的存储中的对象或数组(直到存储是否通过调用相应的释放来显式释放函数).
我的疑问是你不能保证s_MyPlaceHolder[0]
的地址是正确对齐的。
我不认为有什么不好的(在单线程环境中):
#include <cstdlib>
class MyObject
{
static MyObject* instance;
static void release_instance() { delete instance; }
public:
static MyObject& get_instance()
{
if (!instance)
{
instance = new MyObject();
std::atexit(&release_instance);
}
return *instance;
}
};
除了单例和全局通常是一个糟糕的主意(它们倾向于将您的代码与此类对象的存在结合起来,从而加强了代码部分之间的耦合)。
由于您对控制对象的生命周期感兴趣,您可以使用RAII:
class MyObject
{
MyObject() { ... }
~MyObject() { ... }
// Never defined
MyObject(const MyObject&);
void operator=(const MyObject&);
static MyObject* instance = 0;
static void create_instance()
{
if (instance) throw std::logic_error("Instance already created");
else instance = new MyObject();
}
static void release_instance() { delete instance; }
public:
struct InstanceKeeper
{
InstanceKeeper(InstanceKeeper& x) : must_clean(x.must_clean)
{ x.must_clean = false; }
~InstanceKeeper() { if (must_clean) release_instance(); }
private:
friend class MyObject;
InstanceKeeper() : must_clean(true) { create_instance(); }
bool must_clean;
};
friend struct InstanceKeeper;
static InstanceKeeper instance_keeper() { return InstanceKeeper(); }
static MyObject& instance()
{
if (!instance) throw std::logic_error("Instance not created");
return *instance;
}
};
用法:
int main()
{
MyObject::InstanceKeeper k = MyObject::instance_keeper();
MyObject::instance().do_something();
...
}
您甚至可以将InstanceKeeper
对象传递给函数,它具有与std::auto_ptr
相同的行为。
您可能有的任何性能问题都是过早优化的情况。
- C++使用整数的压缩数组初始化对象
- 如何使用cudaMallocManaged在指针位置初始化对象?(C++)
- 在 c++ 中初始化对象
- C++ 手动分配和初始化对象
- 使用运算符"="在C++中用值初始化对象
- 当我不需要数据库中的所有值时,如何部分初始化 c++ 对象?
- 如何初始化对象数组?
- 在C++中使用默认构造函数初始化对象的不同方法
- 使用默认构造函数初始化对象的不同方法
- 是否可以在编译时初始化对象的 C 样式函数指针,以便它调用对象的成员函数?
- 如何在线程中初始化对象,然后在其他地方使用它?
- 在没有默认构造函数时使用垃圾数据初始化对象
- 如果在 C++ 构造函数中以错误的顺序初始化对象数据,会发生什么类型的错误
- 初始化对象以在 C++08 中作为参数传递的首选语法是什么?
- 在 c++ 中复制对未初始化对象的引用
- 在成员变量在另一个文件中发生更改后,调用与初始化对象分开的函数
- 在不放置新运算符的情况下,在预分配的内存上使用虚函数初始化对象 - 这可能吗?如果没有,为什么
- 复制 CTOR 与赋值运算符以初始化对象(性能)
- 当您通过分配初始化C 对象时会发生什么
- 获取未初始化对象成员的地址是否定义良好?