我希望有些类(比如说源代码)自动将自己注册到另一个类(比如目的地)。我的第一个想法是声明一个目标类的静态实例,并在源类中使用静态字段的初始化。
对于真正的问题,它将自动在Windows GUI程序中将表示Windows的类声明为表示应用程序的单例对象,以便应用程序对象的初始化能够注册所有窗口类(在WIN32中)义)。
我可以写一个概念证明,它甚至没有警告就编译和运行(MSVC 2008,gcc 4.8.2,Clang 3.5),但我想知道它是否真的是正确的,或者只是由于静态初始化惨败而导致的未定义行为。我只在SO中找到In C++, Is it good form to write code that executes before main()?,这可能与我的问题松散相关。
短篇小说(完整的可编辑来源):
我有一个目标类说A带有内部Node类(简单链接指针列表),Node *head
字段和registerName方法。
class A {
...
Node * head; // should be NULL
static A instance;
...
我用该C ++片段声明了A的静态实例:
A A::instance;
bool A::registerName(const char *name) {
std::cout << "Register " << name << std::endl;
Node *node = new Node(name, head);
head = node;
return true;
}
然后我通过初始化静态布尔值来注册包含静态char *名称的源类(比如说C):
const char C::name[] = "ClassC";
bool C::inited = A::getInstance().registerName(C::name);
问题是:
由于静态bool C::inited
的初始化取决于静态A A::instance
,它会因为静态初始化失败而导致UB,或者仍然是正确的,因为我只需要在A实例中{{{要初始化为NULL的指针(并且可以在调用任何构造函数之前进行初始化)?
TL / DR:完整来源:
目的地类:
head
实施
class A
{
class Node {
const char *name;
Node *next;
public:
Node(const char *name = 0, Node *next = 0): name(name),
next(next) {};
const char* getName() const { return name; }
Node * getNext() const { return next; }
};
Node *head;
static A instance;
A() {};
~A();
public:
static A& getInstance() { return instance; }
Node *getHead() const { return head; }
bool registerName(const char *name);
friend int main();
};
目的地类(3个不同的文件):
#include "A.h"
#include <iostream>
A::~A(void)
{
Node *next;
while (head != 0) {
next = head ->getNext();
std::cout << "Delete Node for " << head->getName() << std::endl;
delete head;
head = next;
}
}
A A::instance;
bool A::registerName(const char *name) {
std::cout << "Register " << name << std::endl;
Node *node = new Node(name, head);
head = node;
return true;
}
//
#include "A.h"
class B
{
protected:
static bool inited;
static const char name[];
B() {};
virtual ~B() {};
};
//
#include "B.h"
class C :
public B
{
protected:
static bool inited;
static const char name[];
public:
C() {};
virtual ~C() {};
};
实施(2个档案):
#include "B.h"
class D :
public B
{
protected:
static bool inited;
static const char name[];
public:
D() {};
virtual ~D() {};
};
//
#include "C.h"
const char C::name[] = "ClassC";
bool C::inited = A::getInstance().registerName(C::name);
主档案:
#include "D.h"
const char D::name[] = "ClassD";
bool D::inited = A::getInstance().registerName(D::name);
输出:
#include <iostream>
#include "A.h"
int main()
{
A& instance = A::getInstance();
A::Node* node = instance.getHead();
while (node != NULL) {
std::cout << node->getName() << std::endl;
node = node->getNext();
}
return 0;
}
答案 0 :(得分:1)
为了防止由于初始化顺序引起的问题,在函数内部实现单例作为静态变量。请参阅http://www.parashift.com/c++-faq-lite/construct-on-first-use-v2.html。
在您的情况下,使用单例来注册窗口类是令人惊讶的。对于可维护的代码来说绝不是件好事。也许最好手动注册应用程序类中的所有窗口类,例如通过硬编码的初始化函数表。事件循环不应该在Application的构造函数内部运行,因为这会阻止扩展,而是例如在申请方法中。
答案 1 :(得分:1)
这个答案主要是对马库斯的一个实施例的补充。
对于静态A instance
,我使用:
用于定义实例的静态函数(来自Markus的答案):
static A& getInstance() {
static A _instance;
return _instance;
}
A类中的静态引用:A& instance
(用作类中的缓存),在实现文件中定义:
A& A::instance = A::getInitInstance();
对于源类的注册,我设法将所有注册逻辑放入模板基类中:
template<typename T> // T will be the actual subclass
class Base {
protected:
static bool registerSelf() {
A::registerClass(T::name);
return true;
}
Base() {
if (! inited) { // force an access to inited
registerSelf(); // never executed in my tests
}
}
static const bool inited;
};
template<typename T> // in the .h as it is a template
const bool Base<T>::inited = Base<T>::registerSelf();
派生类只需定义static const char * name
,但(至少在MSVC 2008中)它必须是公共的,或Base<T>::registerSelf();
必须明确声明为朋友(无论如何,公开它是有意义的):
class C: public Base<C> {
public:
static const char name[];
// friend bool Base<C>::registerSelf(); if name is not public
};
const char C::name[] = "foo"; // in implementation file
这样,所有在程序中任何地方实际使用的子类都被注册,没有任何静态初始化失败的风险(感谢Markus),并且在我的测试中,如果声明了一个类但未使用它未注册