3

嗨,我有带有一些值的静态 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;
}

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

4

6 回答 6

3

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

于 2012-09-06T12:02:44.323 回答
2

您编写它的方式是访问一个未初始化的元素,因为sqlenumToInt首先评估初始化器;这可能是未定义的行为(取决于迭代器类型的细节)。

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

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

于 2012-09-06T11:58:51.513 回答
1

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

于 2012-09-06T12:00:03.400 回答
0

正如@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

因此,分配了静态变量的内存,但稍后执行初始化,类似于“放置新”机制。您可以在输出中看到,在 init 之后调用了 bar 的 c'tor,但旧值为 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;
}
于 2012-09-06T14:38:21.813 回答
0

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

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

这段代码有两个问题。第一个是因为在到达行之前没有构造迭代器,如果迭代器没有平凡的构造函数(调用operator=未初始化的对象——这在某些情况下不是问题,但代码不会是便携式的)。

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

请注意,在函数内部设置迭代器根本不会向代码添加任何值,因此您可以将设置器放在一边。如果您想要的是引用该元素的迭代器,您可以做多种事情:如果它始终是第一个元素,则sqlenumToInt.begin()在其初始化程序中将其设置为,如果您想引用映射中的特定元素(在初始化的地方,设置sqlenumToInt.find(element)initEnumToIntinitEnumToInt函数作为引用的参数。--这不是必需的,作为一个公共静态变量,函数无论如何都可以访问它,但是通过引用传递它会使依赖关系以及它在代码中显式地在函数中被修改的事实。

于 2012-09-06T12:27:13.503 回答
0
std::map<std::string, int>::iterator();

此行为 构造了一个默认迭代器map<string, int>,因此您defaultIt将只是此默认迭代器的副本。如果你想要第一个地图元素,你应该用sqlenumToInt.begin().

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

于 2012-09-06T12:01:44.740 回答