【问题标题】:Best practice for C++ complex static initialization logicC++ 复杂静态初始化逻辑的最佳实践
【发布时间】:2015-07-31 02:32:05
【问题描述】:

我长期使用 Java,并且对 C++ 比较陌生。因此,在 Java 中,如果在类级别有一些复杂的静态对象(在 Java 中一切都在类级别),我们可以简单地使用静态块来初始化它。例如

public class MyClass extends MyBase {

    public static final Map<String, AComplexClass> STATIC_MAP = new HashMap<String, AComplexClass> ();

    static {
        AComplexClass first = ComplexClassFactory.createComplexType1();
        first.configure("Something", "Something");

        STATIC_MAP.put("key1", first);

        // do some more
    }
}

但是考虑到初始化 C++ 静态变量的限制,如果我想定义相同的 STATIC_MAP,这可能太复杂了,这似乎我需要在 .cpp 文件中为每个 AComplexClass 对象命名变量,然后放到地图上,在代码中产生了相当多的垃圾,也有机会让其他类不小心引用它们。

那么,C++ 等价于这种静态初始化逻辑的最佳实践是什么?我认为这是使用相同的策略,但可能是其他一些风格。

【问题讨论】:

  • 但是考虑到初始化 C++ 静态变量的限制,你在说什么限制?
  • 请注意,任何初始化的静态变量都将在 main() 运行之前完成。如果存在错误或竞争条件,则可能很难追踪。
  • 只需使用控制反转,让您的应用程序生命周期管理复杂对象的创建和销毁。这可以应用于 java 和 c++ 并且您不需要在两种语言之间更改模式并且具有更好的可测试性。

标签: java c++ static


【解决方案1】:

.cpp 文件中的静态初始化

您可以在 C++ 中执行类似的操作,但您需要在 .cpp 文件中进行初始化。您可以将初始化推迟到一个函数,以便您可以进行任何您想要的初始化:

MyClass.h

#pragma once
#include <unordered_map>
#include <string>
#include "AComplexClass.h"

using MyMap = std::unordered_map<std::string, AComplexClass>;

class MyClass {
public:
  const static MyMap STATIC_MAP;
};

MyClass.cpp

#include "MyClass.h"

namespace {    
  MyMap createMap() {
    MyMap map;
    auto first = createComplexType1();
    first.configure("Something", "Something");

    map.emplace("key1", std::move(first));

    // do some more

    return map;
  }
}

const MyMap MyClass::STATIC_MAP = createMap();

ma​​in.cpp

#include "MyClass.h"

int main() {
  auto object = MyClass::STATIC_MAP.at("key1");
}

因为operator[] 需要非常量访问,所以我使用了at()

您需要注意的一件事是static initialization order fiasco,这基本上意味着您应该避免依赖另一个 .cpp 文件中的任何静态初始化。

静态局部变量

避免静态初始化顺序失败的一种方法是使您的地图成为函数中的静态局部变量。这还有一个好处是,如果您愿意,您可以在 .h 文件中定义所有内容:

MyClass.h

#pragma once
#include <unordered_map>
#include <string>
#include "AComplexClass.h"

using MyMap = std::unordered_map<std::string, AComplexClass>;

MyMap createMap() {
  // as before ...
}

class MyClass {
public:
  static const AComplexClass& STATIC_MAP(const std::string& key) {
    const static MyMap map = createMap();

    // Could return the map by reference but may as well use it in this
    // function to make the syntax slightly simpler for the calling code.
    return map.at(key);
  }
};

ma​​in.cpp

#include "MyClass.h"

int main() {
  auto object = MyClass::STATIC_MAP("key1");
}

静态局部变量在第一次使用时会被初始化。

【讨论】:

  • const MyMap MyClass::STATIC_MAP = createMap(); 所以这涉及到副本。
  • @MeowCat2012 此副本将被优化掉。在 C++17 中,这是有保证的,但即使是旧的编译器也会优化掉这个副本。请参阅命名返回值优化 (NRVO) 和复制省略。
  • 谢谢...我以前从不相信编译器。
【解决方案2】:

这是一个组织问题。我不确定每个人都会同意一个单一的“最佳实践”,但这是我将如何处理您正在尝试做的事情。

首先是行

public static final Map<String, AComplexClass> STATIC_MAP = new HashMap<String, AComplexClass> ();

您可以在 C++ 中执行此操作,但您需要将其分解。假设您的课程是在标准 .h(pp) / .cpp 除法中实现的,您会想要:

.h(pp): static Map&lt;String, AComplexClass&gt; STATIC_MAP;

.cpp: Map&lt;String, AComplexClass&gt; MyClass::STATIC_MAP = new HashMap&lt;String, AComplexClass&gt; ();

这将创建您要用于静态对象的内存。但是,您只能使用该语法真正做到这一点 - 无需设置(如果构造函数需要参数,我不知道如何使其工作 - 除非它们是常量,否则我认为您需要一种不同的方法)。对于设置,您有两种选择。

选项 1:将其粘贴在构造函数中

如果你在类中添加另一个静态变量,例如static bool firstObject / bool MyClass::firstObject = true;,那么你可以添加一些效果:

MyClass::MyClass()
{
    if( firstObject )
    {
        // Do your setup
         firstObject = false;
    }
}

这会带来一些开销,但除非您要创建一个 疯狂 数量的这些对象,否则它非常小。

选项 2:设置变量的工厂/朋友类

如果您有另一个明确负责创建您的类的类(例如工厂模式),您可以让该类成为朋友并让它设置值。然而,这假定每个类只有一个这样的工厂 - 尽管通常情况下是这样。

正如您所拥有的那样,您不需要加好友来使用此选项 - 但我想说的是,一般来说,您可能不希望公开非const 静态数据。这是无法追踪的混乱的秘诀,因为任何东西都可以在程序的任何地方修改它们。

【讨论】:

  • “但是,您只能使用该语法真正做到这一点 - 没有设置(如果构造函数需要参数,我不知道如何使其工作 - 除非它们是常量”你可以做到如果你遵循一个函数,你喜欢任何设置。
  • @ChrisDrew 我同意,这可能是一个更符合 Alex Suo 所要求的解决方案 - 我以前没有考虑过这样做。
【解决方案3】:

@ChrisDrew 关于使用额外函数进行初始化的回答只是一个评论:如果函数只是为此创建的并且它非常小,那么使用 lambda 函数可能是一个好主意。例如,当我想初始化一个静态向量时,我的 .cpp 中有这行代码:

//.cpp
vector<int> MyClass::v = [](){vector<int> v; v.reserve(100); return v;}();

【讨论】:

    【解决方案4】:

    user4581301 已经有正确答案但可以改进。

    在 C++ 中,您还可以静态初始化成员,让我们将您的 Java 类转换为 C++ 类:

    class MyClass : public MyBase {
    public:
        static std::map<std::string, AComplexClass> STATIC_MAP;
    };
    

    仅此而已,不多也不少。如您所见,STATIC_MAP 是类的一部分,但未在此处初始化,所以...应该在哪里初始化?课外(似乎奇怪but this is the C++ way):

    std::map<std::string, AComplexClass> MyClass::STATIC_MAP
    {
        {"key1", ComplexClassFactory::createComplexType1("Something", "Something")},
        {"key2", ComplexClassFactory::createComplexType1("Foo",       "Bar")},
        {"key3", ComplexClassFactory::createComplexType1("Epi",       "Blas")},
        {"key4", ComplexClassFactory::createComplexType1("Mortadelo", "Filemon")},
        {"key5", ComplexClassFactory::createComplexType1("Asterix",   "Obelix")},
    };
    

    把它想象成 Java 类上的 static 块,但写在类本身之外。上面的代码显示了 C++ 的一个新特性,由于 C++11 标准,您可以使用 initializer list 填充容器,因此对于您的类的映射,我们将 {key, value} 对的列表传递给 @987654331 @构造函数。键是文字字符串"key?",值是createComplexType1 返回的任何值。

    将静态初始化与类的定义分开允许我们在代码文件(cpp)而不是头文件(h)中执行此初始化,这样做有一些优点,例如更整洁的代码和隐藏细节的可能性类的。

    STATIC_MAP 的初始化和生命周期绑定到 Static storage duration":

    静态存储持续时间。 对象的存储空间在程序开始时分配,在程序结束时释放。该对象仅存在一个实例。所有在命名空间范围内声明的对象(包括全局命名空间)都有这个存储持续时间,加上那些用 static 或 extern 声明的对象。

    您可以观看现场演示Here

    【讨论】:

      【解决方案5】:

      我缺少一件:使 STATIC_MAP 保持不变。您不能在 const 映射上使用 operator[] ,因为如果查找的键不存在,则映射会成功。谨防。 C++ 和 Java 映射之间的巨大差异。

      #include<iostream>
      #include <map>
      #include <memory>
      
      //Need a class for the example
      class AComplexClass
      {
      public:
          virtual ~AComplexClass()
          {
      
          }
          // just to give it something to do. It is a complex class after all.
          virtual std::string toString() = 0;
      };
      
      // Doing this with a map<string, AComplexClass> is trivial. 
      // To make things a bit nastier, let's throw in some abstraction!
      class ChildOfAComplexClass: public AComplexClass
      {
      public:
          std::string toString()
          {
              return " Howdy! I'm a child of AComplexClass!";
          }
      };
      
      // shared_ptr will take care of the garbage collection since we're passing
      // around a hierarchy of complex classes. Normally you wouldn't bother with
      // a pointer and let map handle all of the allocation/deallocation, but I 
      // wanted to make this example hard.
      
      // OK. Non-trivial. 
      
      // OK. It's still trivial.
      
      // How much blood do you want from me?
      std::shared_ptr<AComplexClass> AComplexClassFactory()
      {
          return std::shared_ptr<AComplexClass>(new ChildOfAComplexClass());
      }
      
      // The main event! Declare and load up the map.
      std::map<std::string, std::shared_ptr<AComplexClass>> STATIC_MAP
      {
          {"one", AComplexClassFactory()},
          {"two", AComplexClassFactory()},
          {"three", AComplexClassFactory()},
          {"four", AComplexClassFactory()}
      };
      
      // And a quick test to prove it works.
      int main()
      {
          std::cout << STATIC_MAP["one"]->toString() << std::endl;
          return 0;
      }
      

      编辑:

      Chris Drew 指出 std::map 的 at 方法解决了 const 问题并让用户知道映射中是否不存在键。请注意,处理异常可能非常昂贵,因此我不建议将它们用于流控制。如果搜索不在 map 中的键是一个常见的用例,请使用 map 的 find 方法。

      // The main event! Declare and load up the map.
      const std::map<std::string, std::shared_ptr<AComplexClass>> STATIC_MAP
      {
          {"one", AComplexClassFactory()},
          {"two", AComplexClassFactory()},
          {"three", AComplexClassFactory()},
          {"four", AComplexClassFactory()}
      };
      
      // And a quick test to prove it works.
      int main()
      {
          try
          {
              std::cout << STATIC_MAP.at("one")->toString() << std::endl;
              std::cout << STATIC_MAP.at("five")->toString() << std::endl;
          }
          catch (std::out_of_range &oore)
          {
              std::cout << "And five does not exist in the map!" << std::endl;
          }
          return 0;
      }
      

      【讨论】:

      • 如果你想将地图设为 const,你可以使用.at()
      • @ChrisDrew 很高兴知道,更适合有 Java 经验的人。会更新。
      【解决方案6】:

      也许这不是最好的答案,但它适用于我的情况,所以也许它可以帮助其他人。 它主要基于 Chris Drew 的 anwser(目前已被接受)。在我的情况下,我实际上还想要一个析构函数,因为我正在 Chris Drew 的CreateMap 中打开一个文件,当然我想在程序退出时再次正确关闭。 注意,这个答案并不完全是对所提问题的回答,但我认为它可能会帮助其他遇到这个问题的人获得如何处理复杂静态类型的知识。

      我的诀窍基本上是围绕您想要静态的类型创建一个包装器结构/类。这个包装结构/类你可以给一个构造函数和析构函数。在您想要拥有静态对象的类中,您创建包装器的静态对象而不是类型。在代码中是这样的:

      public class MyClass extends MyBase {
        struct MyMap {
          std::unordered_map<std::string, AComplexClass> internal_map;
          MyMap(){
            auto first = createComplexType1();
            first.configure("Something", "Something");
      
            internal_map.emplace("key1", std::move(first));
      
            // do some more
          }
          ~MyMap(){
            // possible destructor
          }
        }
      
        public static final MyMap STATIC_MAP;
      }
      

      对此可能会有很多改进。也许这并不是这个问题的最佳答案,但我仍然认为它可能会帮助其他人解决这个问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-30
        • 1970-01-01
        • 1970-01-01
        • 2010-09-22
        • 2017-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多