【问题标题】:static initialization of multiple static variables in one function在一个函数中静态初始化多个静态变量
【发布时间】:2012-09-06 11:55:03
【问题描述】:

嗨,我有静态 std::map 和一些值和静态迭代器到这样的默认元素并同时初始化两者:

在.h文件中

class foo
{
    static std::map<std::string, int> sqlenumToInt;
    static std::map<std::string, int> initEnumToInt();
    static std::map<std::string, int>::iterator defaultIt;
};

在 .c 文件中

std::map<std::string, int> foo::sqlenumToInt = initEnumToInt();

std::map<std::string, int> foo::defaultIt = std::map<std::string, int>::iterator();

std::map<std::string, int> foo::initEnumToInt();
{
    std::map<std::string, int> test;
    defaultIt = test.insert(std::make_pair("a", 0)).first
    test["b"] = 2;
    test["c"] = 3;
    test["d"] = 4;
    return test;
}

静态变量的默认初始化顺序是什么。将默认它只是 std::map::iterator() 或迭代器到 sqlenumToInt 的第一个元素??

【问题讨论】:

    标签: c++ static initialization


    【解决方案1】:

    在翻译单元内,静态变量的初始化顺序是明确定义的;静态变量按定义顺序初始化。所以initEnumToInt 运行之前 foo::defaultIt 被初始化。在您的代码中,这将导致未定义的行为,因为在 initEnumToInt 运行时,foo::defaultIt 处于未初始化(但零初始化)状态;然后,您在零初始化对象上调用 operator=,然后调用期望零或未初始化对象的构造函数。

    【讨论】:

      【解决方案2】:

      按照您的编写方式,您正在访问一个未初始化的元素,因为sqlenumToInt 的初始化程序首先被评估;这可能是未定义的行为(取决于迭代器类型的详细信息)。

      如果您想要地图的前面,请在初始化程序中说 defaultIt = sqlenumToInt.begin() 并将其从 initEnumToInt() 中删除。

      (此外,即使您在函数中获得的迭代器也将毫无意义,因为一旦本地地图对象被破坏,它就会变得无效。)

      【讨论】:

        【解决方案3】:

        文件范围变量按照它们的定义顺序进行初始化。在示例代码中,sqlenumToInt 将首先被初始化,调用initEnumToInt,它将defaultIt 设置为在函数调用结束时变为无效的迭代器值(它指向test,它被销毁;@ 987654325@ 获得test副本)。然后显式初始化defaultIt,存储一个默认构造的迭代器。

        【讨论】:

          【解决方案4】:
          std::map<std::string, int>::iterator();
          

          这一行为map&lt;string, int&gt; 构造了一个默认 迭代器,所以你的defaultIt 将只是这个默认迭代器的一个副本。如果你想要第一个地图元素,你应该用sqlenumToInt.begin()初始化它。

          关于初始化的顺序,在一个编译单元内,静态变量的初始化顺序与你定义它们的顺序相同,但不同单元之间的顺序是不确定的。

          【讨论】:

            【解决方案5】:

            initEnumToInt() 里面的这行有问题:

            defaultIt = test.insert(std::make_pair("a", 0)).first
            

            这段代码有两个问题。第一个是因为迭代器没有被构造之前到达该行,它会导致未定义的行为如果迭代器没有一个平凡的构造函数(调用operator= on未初始化的对象——在某些情况下这不是问题,但代码不可移植)。

            该行的第二个问题是您将迭代器设置为引用 local 映射中的元素。函数完成后对该迭代器的任何使用都是未定义的行为。

            请注意,在函数内部设置迭代器根本不会向代码添加任何值,因此您可以将设置器放在一边。如果你想要的是迭代器来引用那个元素,你可以做很多事情:如果它总是 first 元素,那么在它的初始化器中将它设置为sqlenumToInt.begin(),如果你想引用映射中的特定元素(在初始化位置已知,将其设置为 sqlenumToInt.find(element)。如果您希望将其设置为仅在 initEnumToInt 函数内部已知的特定元素,则更改初始化顺序,以便迭代器首先被初始化,并通过引用将它作为参数传递给initEnumToInt函数。--这不是必需的,作为一个公共静态变量,函数无论如何都可以访问它,但是通过引用传递它会产生依赖关系和事实它在代码中显式的函数中被修改。

            【讨论】:

              【解决方案6】:

              如@ecatmur 所写,初始化是逐行执行的。所以:

              1. std::map foo::sqlenumToInt = initEnumToInt();
              2. std::map foo::defaultIt = std::map::iterator();

              为了解释它,我写了一个简单的例子:

              // foo.h
              #pragma once
              
              #include <iostream>
              
              struct bar
              {
                  int value_;
                  bar(int value)
                  {
                      std::cout << "bar()\n";
                      set(value, true);
                  }
                  void set(int value, bool fromConstructor = false)
                  {
                      std::cout << ((fromConstructor) ? "Set from ctor" : "Set from non-ctor")
                          << ", old value is: " << value_ << "\n";
                      value_ = value;     
                  }   
              };
              
              struct foo
              {
                  static bar bar_;
              
                  static bool init()
                  {
                      std::cout << "init begin\n";
                      bar_.set(1);
                      std::cout << "init end\n";
                      return true;
                  }
              };
              
              // main.cpp
              #include "foo.h"
              
              bool b = foo::init();
              bar foo::bar_ = bar(2);
              
              int main()
              {
                  std::cout << foo::bar_.value_ << "\n";
                  return 0;
              }
              

              输出是:

              init begin
              Set from non-ctor, old value is: 0
              init end
              bar()
              Set from ctor, old value is: 1
              2
              

              因此,分配了静态变量的内存,但稍后执行初始化,类似于“放置新”机制。您可以在输出中看到 bar 的 c'tor 在 init 之后被调用,但旧值为 1(将由 init 方法设置)并由于静态初始化而被覆盖为 2。

              因此,您可以通过更改订单静态初始化轻松解决该问题。但是您还有@Kerrek SB 描述的另一个问题:

              (此外,即使您在函数中获得的迭代器也会 毫无意义,因为它一旦本地地图变得无效 对象被销毁。)

              纠正您的情况的变体之一:

              class foo
              {
                  typedef std::map<std::string, int> Map;
              
                  static bool initEnumToInt();
              
                  static Map sqlenumToInt;
                  static Map::iterator defaultIt;
                  static bool inited;
              };
              
              foo::Map foo::sqlenumToInt;
              foo::Map::iterator defaultIt = foo::Map::iterator();
              bool foo::sqlenumToInt = initEnumToInt();
              
              bool foo::initEnumToInt();
              {
                  defaultIt = sqlenumToInt.insert(std::make_pair("a", 0)).first;
                  sqlenumToInt["b"] = 2;
                  sqlenumToInt["c"] = 3;
                  sqlenumToInt["d"] = 4;
                  return true;
              }
              

              【讨论】:

                猜你喜欢
                • 2018-03-15
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-05-13
                • 2011-08-22
                • 2010-12-22
                • 2019-03-04
                • 1970-01-01
                相关资源
                最近更新 更多