【问题标题】:Initialization of static member with same type as class (static initialization order problem)初始化与类相同类型的静态成员(静态初始化顺序问题)
【发布时间】:2020-01-31 11:11:48
【问题描述】:

假设我有一个类Super,其中我有一个Super 类型的静态成员,它只定义了一个常用的Super 实例

// super.hpp
class Super
{
public:
    Super(const double a):
        m_a(a)
    {}

    double a() const { return m_a; }

    static const Super thing; // use this a lot in code

private:
    double m_a;
};

实施

// super.cpp
#include "super.hpp"

const Super Super::thing = Super(1.0); // definition

在这一点上我认为一切都很好,但如果不是,请纠正我。

现在我也有Super 的子类,具有类似的静态成员

// sub.hpp
#include "super.hpp"

class Sub : public Super
{
public:
    Sub(const double a):
        Super(a)
    {}

    explicit Sub(const Super& obj):
        Super(obj)
    {}

    static const Sub thing;
};

实施

// sub.hpp
#include "sub.hpp"

const Sub Sub::thing = Sub(Super::thing); // WORKS IN MYSTERIOUS WAYS

最后是一个示例用法

// main.cpp
#include <iostream>
#include "sub.hpp"

int main()
{
    Super super = Super::thing;
    std::cout << super.a() << std::endl;

    Sub sub = Sub::thing;
    std::cout << sub.a() << std::endl;
}

当我使用以下设置编译它时

// CMakeLists.txt
project (hello)
add_executable(hello main.cpp super.cpp sup.cpp)

我得到了预期的输出

$ ./hello 
1
1

但是如果我在CMakeLists 中更改cpp 文件的顺序(sub.cppsuper.cpp 之前)

// CMakeLists.txt
project (hello)
add_executable(hello main.cpp sup.cpp super.cpp)

我明白了

$ ./hello 
1
0

认为这是static initialization order ‘fiasco’(?)的一个例子,我对它发生的原因有所了解。

所以问题变成了:有什么方法可以得到关于未初始化静态的警告,或者有什么方法可以避免这个问题?

我已经阅读了How do I prevent the “static initialization order problem”?,但我希望能够保留Sub::thing 接口,并避免将其替换为Sub::thing()

【问题讨论】:

  • 我的构造函数非常简单(它们的主体是空的,我只是分配初始化列表中的所有值),所以我可能可以这样做,但我需要更多关于如何做到这一点的指导。我对constexpr不是很熟悉。
  • 我尝试将thing 设为inline static 而不是static,但由于Super 的类型不完整,因此出现错误。
  • 是的,我认为问题在于,编译器没有看到类的完整定义。这很奇怪,因为那时它不需要它。如果thing 不是班级的成员,而只是班级之外的全球成员,它应该可以工作。我删除了我的建议。我稍后会尝试正确执行此操作。
  • 我找到了一个可能有用的类似问题的答案:stackoverflow.com/a/32134757/1850917
  • 好吧,我的意思是this,但同样,这取决于您的用例。

标签: c++


【解决方案1】:

有什么方法可以得到关于未初始化静态的警告,

我不知道

或者有什么方法可以避免这个问题?

  • 您可能会避免使用全局成员,并使用延迟初始化将全局包装在一个函数中,因此更改:

    // header
    class Super {
        static const Super thing;
        // ...
    };
    //  cpp file
    const Super Super::thing = Super(1.0); // definition
    

    // header
    class Super {
        static const Super& thing();
        // ...
    };
    //  cpp file
    const Super& Super::thing() { static const Super instance{1.0}; return instance; }
    

    类似

    class Sub : public Super
    {
    public:
        // ...
        static const Sub thing;
    };
    
    // sub.cpp
    const Sub Sub::thing = Sub(Super::thing); 
    

    通过

    class Sub : public Super
    {
    public:
        // ...
        static const Sub& thing();
    };
    
    // sub.cpp
    const Sub& Sub::thing() { static const Sub instance(Super::thing()); return instance; }
    
  • 或者将所有全局放在一个翻译单元中,因为顺序是有保证的。

    // global.cpp
    #include "super.hpp"
    #include "sub.hpp"
    
    const Super Super::thing = Super(1.0);    // Defined in order
    const Sub Sub::thing = Sub(Super::thing); // so you have control
    

【讨论】:

  • 这行得通,但我的目标是保留Super::thing 接口,这是当前接受的答案。不过感谢您的建议!
  • 仍有可能将 2 全局放在同一个文件中,因为初始化顺序在 TU 内得到保证。
【解决方案2】:

根据您的 cmets,我做了以下操作:

  • 构造函数constexpr
  • 制作了static成员constexpr inline(你必须将它们放入头文件,而不是.cpp),所以需要C++17

现在,您的静态变量是 constexpr,因此它们将被静态初始化,因此静态初始化顺序的失败不会发生在它们身上。

所以,这个解决方案对我有用:

class Super
{
public:
    constexpr Super(const double a):
        m_a(a)
    {}

    double a() const { return m_a; }

    static const Super thing;

private:
    double m_a;
};

constexpr inline Super Super::thing = Super(1.0);

class Sub : public Super
{
public:
    constexpr Sub(const double a):
        Super(a)
    {}

    constexpr explicit Sub(const Super& obj):
        Super(obj)
    {}

    static const Sub thing;
};

constexpr inline Sub Sub::thing = Sub(Super::thing);

#include <iostream>

int main()
{
    Super super = Super::thing;
    std::cout << super.a() << std::endl;

    Sub sub = Sub::thing;
    std::cout << sub.a() << std::endl;
}

【讨论】:

  • 这是我自己研究的确切解决方案,而且似乎效果很好。我不完全理解如何在标题中包含定义并且不会出现多个定义错误,但只要它有效!
  • @FilipS.:这就是为什么需要inline。如果你删除它,你会得到链接器错误。
猜你喜欢
  • 2011-07-18
  • 1970-01-01
  • 1970-01-01
  • 2015-05-18
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多