2017-10-12 77 views
2

前一段时间,我用一组嵌套类编写了一些代码。今天看看它,我想知道为什么它编译时,我实例化类型的对象C.这段代码为什么编译时不会抱怨构造函数?

我的困惑是这样的。 B有一个私人构造函数。这个构造函数的实现关心A的构造,但是A的构造需要B的一个实例。我觉得它是一个鸡和鸡蛋的场景。 B的建设需要建造A,这需要建造B等等。

我有以下的类 - 剥离右后卫演示问题:

// ******* A ******** // 
class A { 
public: 
    A(A& _Parent, int id); 
private: 
    A& Parent; 
}; 

inline A::A(A& _Parent, int id) 
: Parent(_Parent) 
{ 
} 


// ******* B ******** // 
class B:public A { 
public: 
    static B& GetInstance(); 
private: 
    B(); 
}; 

inline B::B() 
: A(B::GetInstance(), 0) 
{ 
} 

inline B& B::GetInstance() 
{ 
    static B b; 
    return b; 
} 


// ******* C ******** // 
class C:public A { 
public: 
    C(); 
}; 

inline C::C() 
: A(B::GetInstance(), 0) 
{ 
} 
+0

是的。我认为这是不确定的行为,但像大多数编译器实现静态函数/内部范围对象的建设,这滑过裂缝,实际工作... –

+0

注:也许这只是你的样品中存在的问题,而不是真实的代码;但是会导致未定义行为将静态对象放入内联函数中(因为静态对象对于不同的翻译单元将不同,但内联函数对于所有单元必须相同)。希望在真正的代码中,'B :: GetInstance()'没有在头文件中实现。作为一个经验法则,尽量避免提及任何静态对象的内联函数 –

+0

有了'gcc'这也引发运行时异常[__gnu_cxx :: recursive_init_error(https://gcc.gnu.org/onlinedocs/gcc- 4.6.3 /的libstdC++/API/a00064.html)。所以即使这个问题没有在编译时被捕获,初始化也会失败。 – Jonesinator

回答

2

的问题缩小了:

inline B& B::GetInstance() 
{ 
    static B b; 
    return b; 
} 

inline B::B() 
: A(B::GetInstance(), 0) 
{ 
} 

线static B b;创建B对象第一次函数叫做。然而,b的建设,然后呼吁GetInstance,其中达到线static B b;b仍在建设中。

这种情况由C++ 14 [stmt.dcl]/4覆盖:

[...]这样的变量被认为是它的初始化完成时初始化。 [...]如果控制在初始化变量时递归地重新输入声明,则的行为未定义

我删除了一部分,讨论如果引发异常会发生什么,或者如果两个不同的线程同时尝试初始化静态变量。该标准确实允许本地静态变量在函数第一次调用之前被初始化,但即使实现这样做,同样的问题会出现递归控制重新输入声明的问题。


由于未定义的行为,任何事情都可能发生。一个可能的结果是它似乎按预期工作。该标准并不要求在编译时进行诊断 - 这是一个难以分析的问题。如注释中所示,一个版本的gcc在运行时检测到此异常并引发异常。也许你的原始编译器通过有一个标志来指示执行是否已经到达那条线,并在调用构造函数之前设置标志来实现本地静态。

标准中存在未定义行为的基本原理是避免在实现实现定义良好的行为(在这种情况下,确保本地静态只初始化一次的方法)上设置约束。

当然,您应该找到解决问题的方法,因为未定义的行为在将来可能以不同的方式表现出来,而且难以预测。

1

虽然您没有显示任何此类代码,但让我们假设某些尝试调用C的默认构造函数或以其他方式调用B::GetInstance()

GetInstance()的第一条语句是static B b;。由于这是我们第一次到达此处,因此需要通过调用默认构造函数B::B()来初始化对象b

B::B()所做的第一件事情是致电GetInstance(),然后打算将结果传递给构造函数A::A(A&, int)

这在声明static B b;再次把我们带回。并且C++标准规定:([stmt.dcl]/4):

动态初始化静态存储持续时间([basic.stc.static])或线程存储持续时间([basic。 stc.thread])是在控件第一次通过其声明时执行的;这样的变量在初始化完成时被认为是初始化的。如果控制器在变量被初始化时递归地输入声明,则行为是不确定的。

未定义行为意味着什么事情都可能发生。它看起来可能工作,它可能会崩溃或挂起您的程序,或者它可能会初始化不正确并继续。

相关问题