在 std::initializer_list 中使用类似联合的 class

using a union-like class in an std::initializer_list

在下面的代码中,我展示了类似联合的 class 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

编辑: 这个 post 的历史:我的 posting 来自对在 std::initializer_list 中存储编译时可推导的异构对象的问题的明确答案的需要。这个问题之前已经被问过很多次,也有很多次尝试回答(参见 Heterogeneous containers in C++). The most simplistic answer is to use polymorphism, but this ignores the power of being able to define a type at compile time (templates). Besides, heterogeneous, non-related objects grouped together polymorphically means a lot of derived data members are useless, which sows usage and maintenance confusion downstream. Other advice given was to use boost::any or boost::variant, but this has the same weakness as polymorphism and reduces message declaration clarity. Another attempt at container object heterogeneity was the use of std::tuple, but although an initializer_list can certainly contain tuples, this approach too ignores compile-time type resolution. I even found a paper written in 1999 called Heterogeneous, Nested STL Containers in C++ ,它使用模板模板参数来解决异构性问题。毕竟,我选择了 class-like unions ,它导致我在这里 posting。Class-like union for non-related/heterogeneous container objects 具有完美的消息声明清晰度,没有对象大小歧义,并且编译时模板可用,它导致优秀的下游维护场景。

Edit2:(5 周后)这是发生的事情。 1) 鉴于此 posting 中的建议,我实施了一个完整的 class 类联合解决方案。结果是乏味和笨拙的,“标签”被用来识别为每个新功能调用哪个子方法。关于代码维护的低等级。 2) c++17 接受了 std::variant。由于目前尚未在 VS2015 Update 2 中实现,我开始使用 boost::variant。请参阅 ,它使用访问者模式来允许访问已初始化的变体成员和成员函数。这消除了“标签”开关和变体“获取”调用。底线:我放弃了类似 class 的联合并采用变体来创建可维护代码,该代码使用 initializer_list 来存储变体成员功能,所有功能都在编译时可初始化(阅读:高度可维护)。

好吧,我感觉很慷慨,我自己创建了自定义联盟,所以他是一些可以帮助您设置的东西。我已经重写了您的 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;
  };
};

您做错的一件事是跳过 BC 的构造和破坏,直接进入内部变量。您应该始终正确地创建和销毁类型,即使它们可能微不足道。虽然这可能会奏效,但没有正确初始化这些对象只会带来麻烦(如果您将来更改 BC 也会更容易)。

为了更容易使用 class,我为 std::stringint 添加了适当的构造函数以及赋值运算符。因为现在我们可以按照我们想要的方式构造对象,所以您的 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;
}

我鼓励您修改 BC 以使用构造函数来构建它们的内部结构,而不是依赖 S