静态 std::map 成员变量在 C++ 中安全吗?

Are static std::map member variables safe in C++?

提问人:Mr.C64 提问时间:9/5/2023 最后编辑:Mr.C64 更新时间:9/5/2023 访问量:112

问:

假设我想实现一种查找表,例如:std::map<std::string, int>

// Utils.hpp header

class Utils
{
public:
    static std::map<std::string, int> NameToId;
   
    static int GetIdForName(const std::string & name)
    {
        auto it = NameToId.find(name);
        if (it != NameToId.end())
            return it->second;
        else
            return -1; // not found
    }
};

在相应的 .cpp 文件中:

// Utils.cpp

#include “Utils.hpp”

std::map<std::string, int> Utils::NameToId = {
    {“foo”, 10},
    {“bar”, 20},
    …
};

有这样的成员变量安全吗?static std::map

这会导致由于(和)动态初始化而导致细微的错误吗?std::mapstd::string

如果该表在编译时是已知的并且是只读的,那么用类似 的东西替换 of 的使用并使用线性搜索(如果对按字符串键排序,则使用二进制搜索)来查找与字符串关联的整数值会更好/更安全吗?std::mapstd::array<std::pair<std::string_view, int>>

C++ 静态 标准映射

评论

4赞 463035818_is_not_an_ai 9/5/2023
为什么不安全?
3赞 teapot418 9/5/2023
静态初始化顺序惨败。您不应该使用之前运行的其他静态内容中的映射。main
1赞 selbie 9/5/2023
@teapot418 - 我会用“来自不同源文件中定义的其他静态内容”来增强您的陈述。
1赞 Nikos Athanasiou 9/5/2023
FWIW:我在生产中看到了错误。我已经从 redis++ 客户端中删除了这样一个静态成员,也许我提交中的措辞不是最好的,但你可以看看。用户必须编写古怪的代码或有恶意的意图来实现它,但肯定是可行的。感谢上帝的地址清理器,因为当这样的事情发生在多线程代码中时,几乎没有希望解开混乱
1赞 ildjarn 9/5/2023
为什么不呢?应该/可以吗?因为它可能会改变答案NameToIdconst

答:

3赞 Nikos Athanasiou 9/5/2023 #1

这是我在 redis 客户端中修复的一个错误:

客户端类可以创建订阅者对象(您需要一个客户端对象来创建订阅者):

class Client {

public:

  Subscriber subscriber() { ... }
};

订阅者包含这样一个(无序但相同)静态映射:

class Subscriber {
public: 
  static std::map<...> table; 
}

现在,当您将两件事结合起来时,乐趣就开始了:

  1. 创建一个静态全局客户端(我没有说遵循最佳实践会发生错误)
  2. 确保该类的某个方法用于进行一些查找(因为我们不喜欢 和 )。好吧,实际上它使用静态方法,并使用静态成员,但无论如何......ClientSubscriber::tableswitchif elseSubscriber

按初始化-销毁顺序排列的是

  1. 创建客户端
  2. 创建订阅者
  3. 销毁订阅者
  4. 销毁客户端

在步骤 3 和 4 之间,客户端可能有一些未处理的消息队列,当它处理它们时,它将不得不使用一个死的静态属性。客户端和静态成员都位于全局空间中,我们只是碰巧在订阅者静态成员之前初始化客户端。

再加上一个客户端可能有多个订阅者并行运行的事实(公平地说,在处理东西之前有一个线程在运行,所以客户端对象必须在析构函数完成之前处理剩余的数据 - 如果没有异步计算延迟析构函数,则该错误是不可观察的),并且您有一种非常好的方式来放松对清理程序报告的思念。享受


结论:你所拥有的不是防弹的。在类外如何使用或访问该静态内容时要非常小心。如果只有非静态方法使用它,我上面描述的错误就不会发生。如果公共静态方法使用它,或者如果成员本身是公共的,则要怀疑它如何与其他静态对象混合。

评论

1赞 Mr.C64 9/5/2023
感谢您分享您的经验。我想到了那些微妙的交互,就像你描述的那样:代码开始很简单,然后添加不同的组件,它们彼此交互,并与它们的静态方法和成员变量交互,并且可能会发生微妙的错误。
1赞 Nikos Athanasiou 9/7/2023
@Mr.C64 由于您提到了 + 线性运行时搜索作为替代方案,请注意,在相关的提交中,我包含了一个 on strings 示例作为建议std::arrayswitch