许多C ++程序员都遭受了与全局C ++对象初始化/清理的激烈冲突。最终我找到了一个足够好的解决方案来解决这个问题,我多年来一直在使用它(而且很兴奋)。我的问题是:这个解决方案完全符合C ++标准,还是“平台/实现依赖”?
问题
一般来说,全局对象存在两个主要问题:
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()
来获取它的引用。而且,BTW,这个函数调用很容易被编译器内联。所以这个方法看起来一切都好。我只是好奇它是否符合C ++标准的观点。
答案 0 :(得分:3)
我发现这有两个问题,一个是合法性问题,一个是可用性问题。
第一个问题是对齐:MyObject::s_pMyPlaceholder
无法保证适当对齐以容纳MyObject
。
第二个问题是您将自己局限于MyObject
类型的单个对象。创建第二个,你已经覆盖了第一个,没有任何警告。
我建议使用boost::optional
来延迟对象的初始化。
答案 1 :(得分:2)
我认为您没有正式保证您的解决方案适用于每个兼容的实现,因为C ++标准不保证静态分配的char数组与任何相同大小的对象所需的对齐。
答案 2 :(得分:1)
从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
具有相同的行为。
您可能遇到的任何性能问题都是过早优化的情况。