在std :: initializer_list中使用类似union的类

时间:2016-06-06 20:52:33

标签: c++11 unions initializer-list

在下面的代码中,我展示了类似union的类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当然可以包含元组,但这种方法也忽略了编译时类型解析。我甚至在1999年发现了一篇名为Heterogeneous, Nested STL Containers in C++的论文,该论文使用模板模板参数来解决异质性问题。毕竟,我选择了类似工会的工会,导致我在这里发帖。非相关/异构容器对象的类类联合具有完美的消息声明清晰度,没有对象大小模糊,并且具有编译时模板能力,并且可以实现出色的下游维护方案。

Edit2 :( 5周后)以下是发生的事情。 1)根据这篇文章中的建议,我实施了一个完全类的联合解决方案。结果是繁琐和笨拙的,使用'tag'来识别为每个新功能调用哪个子方法。代码维护等级低。 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'调用。结论:我删除了类似类的联合并采用了variant来创建可维护的代码,该代码使用initializer_list存储变体成员功能,所有这些功能都可以在编译时进行初始化(读取:高度可维护)。

1 个答案:

答案 0 :(得分:1)

好吧,我感觉很慷慨,而且我自己制作了自定义工会,所以他可以帮助你设置一些东西。我已将您的await结构重写为更加合规和可用。 (我已经通过评论进行了更改)

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的构造和破坏,直接进入内部变量。你应该总是正确地创建和销毁类型,即使它们可能是微不足道的。虽然这可能会有效,但是没有正确初始化这些对象只会造成麻烦(如果您将来更改CB,也会更容易)。

为了更容易地使用该类,我在Cstd::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