代码之家  ›  专栏  ›  技术社区  ›  rtischer8277

在std::initializerlist中使用类union

  •  0
  • rtischer8277  · 技术社区  · 8 年前

    在下面的代码中,我展示了类S,它包含两个不相关的结构B和C。我展示了如何实例化非POD std::string并再次删除它,然后将S切换到S::CC并设置num int。

    #include <vector>
    #include <string>
    #include <iostream>
    #include <memory>
    
    struct B
    {
      B() {}
      ~B() {}
      std::string str;
      void Func1() {}
    };
    
    struct C
    {
      C() {}
      ~C() {}
      int num;
      void Func2() {}
    };
    
    struct S
    {
      S() { tag = CC; }
      S( const S& s ) 
      {
        switch( s.tag )
        {
          case BB:
            new ( &b.str ) std::string;
            b.str = s.b.str;
            break;
    
          case CC:
            c.num = s.c.num; 
    
          default:
            break;
        }
      }
    
      ~S() 
      {
        switch( tag )
        {
          case BB:
            b.str.~basic_string< char >();
            break;
    
          case CC:
            c.num = 0;
            break;
    
          default:
            break;
        }
      }
    
      enum { BB, CC } tag;
      union
      {
        B b;
        C c;
      };
    };
    
    struct H
    {
      H( std::initializer_list< S > initializerList ) : initListVect( initializerList ) {}
      std::vector< S > initListVect;
    };
    
    int main()
    {
      S s;
      s.tag = S::BB;
      new ( &s.b.str ) std::string; // docs say use new placement to create memory
      s.b.str = "bbb";
      s.b.str.~basic_string< char >(); // string usage in B ok
    
      s.tag = S::CC;
      s.c.num = 333; // int usage in C ok
    
      H h {  }; // what should the init list be if I wanted 3 list elements S::BB, S::CC, S::BB?
    
      return 0;
    }
    

    然而,我的目标是在std::initializer_list中使用S。我不知道初始化h的格式应该是什么。如果我想用这些S::BB,S::CC,S:,BB初始化h,参数应该是什么?

    我的编译器是VS2015。

    编辑: 这篇文章发布了历史:我的帖子来自于对在std::initializer_list中存储编译时可推断的异构对象问题的明确回答。这个问题以前被问过很多次,也有很多次尝试回答(参见 Heterogeneous containers in C++ ). 最简单的答案是使用多态性,但这忽略了能够在编译时定义类型(模板)的强大功能。此外,以多态方式分组在一起的异构、无关对象意味着许多派生数据成员都是无用的,这给下游的使用和维护带来了混乱。给出的其他建议是使用boost::any或boost::variant,但这与多态性有相同的缺点,会降低消息声明的清晰度。容器对象异构性的另一种尝试是使用std::tuple,但尽管initializer_list确实可以包含tuple(元组),但这种方法也忽略了编译时类型解析。我甚至发现了一篇1999年写的论文 Heterogeneous, Nested STL Containers in C++ 它使用模板模板参数来解决异构问题。在这一切之后,我选择了班级工会,这导致我在这里发帖。用于非相关/异构容器对象的类联合具有完美的消息声明清晰性,没有对象大小的模糊性,并且可以使用编译时模板,因此可以实现出色的下游维护场景。

    编辑2:(5周后)下面是发生的事情。1) 根据这篇帖子中的建议,我实现了一个类完整的工会解决方案。结果是冗长而笨拙的,标签被用来标识为每个新功能调用哪个子方法。代码维护等级低。2) c++17已接受std::variant。由于目前在VS2015 Update 2中还没有实现,所以我开始使用boost::variant。看见 What is the right c++ variant syntax for calling a member function set to a particular variant? 它使用访问者模式来允许访问初始化的变量成员和成员函数。这消除了标记开关和变量get调用。底线:我放弃了类状的union,并采用了variant来创建可维护的代码,该代码使用initializer_list来存储变量成员功能,所有这些功能在编译时都是可初始化的(读作:高度可维护)。

    1 回复  |  直到 7 年前
        1
  •  1
  •   kmdreko    8 年前

    好吧,我觉得很慷慨,我自己也成立了海关联盟,所以他是一些可以帮助你建立起来的东西。我已经改写了你的 S 结构更加兼容和可用。(我做了一些修改,并用注释标记)

    struct S
    {
      S() : tag(CC) // initializer
      {
        new (&c) C; // make C object
      } 
      S(int num) : tag(CC) // added integer constructor
      {
        new (&c) C;
        c.num = num;
      }
      S(const std::string& str) : tag(BB) // added string constructor
      {
        new (&b) B; 
        b.str = str;
      }
      S( const S& s ) : tag(s.tag)
      {
        if (tag == CC)
        {
          new (&c) C; // construct c
          c.num = s.c.num;
        }
        else if (tag == BB)
        {
          new (&b) B; // construct b, not b.str
          b.str = s.b.str;
        }
      }
      S& operator= (const S& s) // added assignment operator
      {
        if (tag == s.tag) // just copy b or c
        {
          if (tag == CC)
            c = s.c;
          else
            b = s.b;
        }
        else // reconstruct b or c
        {
          if (tag == CC)
          {
            c.~C(); // destroy c
            new (&b) B; // construct b
            b.str = s.b.str;
          }
          else
          {
            b.~B(); // destroy b
            new (&c) C; // construct c
            c.num = s.c.num;
          }
          tag = s.tag;
        }
    
        return *this;
      }
    
      ~S() 
      {
        if (tag == CC)
        {
          c.~C(); // destroy c
        }
        else if (tag == BB)
        {
          b.~B(); // destroy b, not b.str
        }
      }
    
      enum { BB, CC } tag;
      union
      {
        B b;
        C c;
      };
    };
    

    你做得不正确的一件事是跳过 B C 直接计算内部变量。您应该始终正确地创建和销毁类型,即使它们可能微不足道。虽然这可能可行,但不正确初始化这些对象只会带来麻烦(如果更改也会更容易 B C 未来)。

    为了简化类的使用,我为添加了适当的构造函数 std::string int 以及赋值运算符。因为现在我们可以按照我们想要的方式构造对象 main() 可能如下所示:

    int main()
    {
      S s;                    // default S
      s = std::string("bbb"); // set to string
      s = 333;                // set to number
    
      // use initialization list
      H h { std::string("bb"), 33, std::string("bb") }; 
    
      return 0;
    }
    

    我鼓励你修改 B C 使用构造函数来构建其内部构件,而不是依赖于 S .