当前位置: 首页 > 知识库问答 >
问题:

在std::initializer\u列表中使用类union的类

宋昊然
2023-03-14

在下面的代码中,我展示了类似联合的类S,它包含两个不相关的结构B和C。我展示了如何实例化非POD std::字符串并再次删除它,然后将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\u列表中使用S。我不知道初始化h的格式应该是什么。如果我想用这些S::BB,S::CC,S::BB初始化h,参数应该是什么?

我的编译器是VS2015。

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

Edit2:(5周后)发生的事情如下。1) 根据这篇帖子中的建议,我实现了一个完整的类式联合解决方案。使用“tag”来标识为每个新功能调用哪个子方法,结果既单调又笨拙。代码维护等级较低。2) c 17已接受std::variant。由于VS2015更新2中目前尚未实现这一点,我开始使用boost::variant。请参见调用特定变量的成员函数集的正确c变量语法是什么?它使用访问者模式允许访问初始化的变量成员和成员函数。这消除了“标记”开关和变量“get”调用。一句话:我放弃了像union这样的类,并采用variant来创建可维护的代码,该代码使用initializer\u list来存储变量成员功能,所有这些功能都可以在编译时初始化(阅读:高度可维护)。

共有1个答案

仉运乾
2023-03-14

好吧,我感觉很慷慨,我自己也做了自定义联合,所以他是一些可以让你设置的东西。我已经重写了您的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::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;
}

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

 类似资料:
  • 刚接触C语言的我,正试图了解initializer\u list。 我正在制作一个Matrix类,它有效地存储了一个双值的二维数组。我没有在结构层面上得到这个项目。就像好吧,我们制作了一个Matrix类,它本质上存储了一个2D数据数组。但是它需要能够存储任何大小的数组,所以它必须使用动态分配的数组。但是std::array是不允许的。 我不知道如何访问i_list中的项目。如果它们像 然后根据我看

  • 我试图用C(11/14)实现fortran的重塑功能,并设计了一个函数。此函数接受两个std::initializer\u列表。第一个initializer\u列表给出了我用来初始化多维数组的初始值。第二个initializer\u列表给出了数组每个维度的大小。我写了一份这样的草稿 这个实现需要给定的非类型模板参数int D,但我想要不带D的东西,比如重塑({1,2,3,4,5,6},{2,3})

  • GCC的实现销毁从返回完整表达式末尾的函数返回的数组。这是正确的吗? 该程序中的两个测试用例都显示了在可以使用该值之前执行的析构函数: 我认为这个计划应该奏效。但基本标准有点复杂。 return语句初始化返回值对象,就像它被声明一样 这将初始化给定系列初始化器中的一个临时初始化器列表及其底层数组存储,然后从第一个初始化器列表初始化另一个初始化器列表。阵列的生存期是多少?“数组的生存期与初始化器列表

  • 考虑代码 输出是 据我所知,值被隐式转换为一个(第一个输出),然后初始化器构造函数开始工作(第二个输出)。我的问题是为什么会这样?标准构造函数不是更好的匹配吗?一、 例如,我本以为这个片段的输出就是ctor。 PS:如果我将构造函数标记为,那么是唯一调用的构造函数,因为整数现在不能隐式转换为。

  • 在c 11的当前状态下(比如gcc 4.7.2),当我需要一个可以接受变量参数的构造函数时,我应该如何选择使用变量模板还是使用std::initializer\u list?

  • 如何修复此问题: 如果T不是类类型,而是: 错误:“int”不是类、结构或联合类型24 |使用类型=std::conditional\u tstd::is\u class\u v 所以我不需要尝试调用错误的表达式,但是如何调用呢?