在std :: initializer_list中使用类似union的类 [英] using a union-like class in an std::initializer_list

查看:328
本文介绍了在std :: initializer_list中使用类似union的类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的代码中,我展示了类似于类的类S,其中包含两个非相关的结构B和C.我显示如何实例化非POD std :: string并再次删除它,然后切换到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(){ta​​g = 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;

默认值:
break;
}
}

〜S()
{
switch(tag)
{
case BB:
b b.str。〜basic_string& char>();
break;

case CC:
c.num = 0;
break;

默认值:
break;
}
}

枚举{BB,CC}标签;
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 {}; //如果我想要3列表元素S :: BB,S :: CC,S :: BB,那么init列表应该是什么?

return 0;
}

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



我的编译器是VS2015。 p>

编辑:
这篇文章的历史:我的发布来自一个需要一个确定的答案的存储编译时可推导的异构对象的问题在std :: initializer_list。此问题之前已被问过很多次,并且已经尝试过很多次(请参阅 C ++中的异构容器)。最简单的答案是使用多态性,但这忽略了能够在编译时定义类型(模板)的能力。此外,异构的,不相关的对象组合在一起多态意味着大量的派生数据成员是无用的,这使得下游的使用和维护混乱。给出的其他建议是使用boost :: any或boost :: variant,但是这与多态性具有相同的弱点并且降低了消息声明的清晰度。另一个尝试容器对象异构是使用std :: tuple,但虽然initializer_list可以肯定包含元组,这种方法也忽略了编译时类型分辨率。我甚至发现了一篇在1999年写的文章,名为 C ++中的异构,嵌套STL容器其使用模板模板参数来解决异质性问题。毕竟,我解决了类的工会,导致我的职位在这里。用于非相关/异构容器对象的类类型联合具有完美的消息声明清晰度,没有对象大小模糊性,并且是编译时可模板化的,并且它导致出色的下游维护场景。



Edit2:(5 weeks later)这是发生了什么事。 1)我实现了一个完整的类似联合解决方案给出的建议在此发布。结果是乏味和笨拙与'标签'被用于识别要调用每个新功能的子方法。低级关于代码维护。 2)c ++ 17已接受std :: variant。因为目前还没有在VS2015 Update 2中实现,我设置了关于使用boost :: variant。请参阅什么是正确的c ++变体语法用于调用成员函数设置为特定的变体?,它使用访问者模式允许访问初始化的变量成员和成员函数。这消除了标签开关和变量get调用。底线:我删除了类似的联合和采用变体创建可维护的代码,使用initializer_list来存储变量成员功能,所有在编译时可以初始化(读:高度可维护)。

解决方案

好吧,我感觉很大,我自己做了自定义工会,所以他会有一些东西,让你设置。我已经重写了你的 S 结构,以更加兼容和可用。 (我已更改标记的评论)

  struct S 
{
S (CC)//初始化程序
{
new(& c)C; // make C object
}
S(int num):tag(CC)//添加整数构造函数
{
new(& c)C;
c.num = num;
}
S(const std :: string& str):tag(BB)//添加的字符串构造函数
{
new(& 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)//添加赋值运算符
{
if(tag == s.tag)//只复制b或c
{
if == CC)
c = sc;
else
b = s.b;
}
else //重构b或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
}
}

枚举{BB,CC}标签;
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; //设置为数字

//使用初始化列表
H h {std :: string(bb),33,std :: string(bb)};

return 0;
}

我鼓励您修改 B C 使用构造函数构建其内部结构,而不是依靠 S


In the code below I show union-like class S which contains two non-related structs B and C. I show how to instantiate the non-POD std::string and delete it again and then switch S to S::CC and set the 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;
}

My goal, however, is to use S in an std::initializer_list. I don’t know what the format should be for initializeing h. What should the arguments be if I wanted to initialize h with these S::BB, S::CC, S::BB?

My compiler is VS2015.

Edit: This post’s history: my posting comes from a need for a definitive answer to the question of storing compile-time-deduceable heterogeneous objects in an std::initializer_list. This question has been asked many times before and there have been many attempts at answers (see 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++ which uses template template arguments to solve the heterogeneity problem. After all this, I settled on class-like unions which led to my posting here. Class-like unions for non-related/heterogeneous container objects has perfect message declaration clarity, no object size ambiguity, and is compile time template-able, and it leads to excellent downstream maintenance scenarios.

Edit2: (5 weeks later) Here is what has happened. 1) I implemented a full class-like union solution given the advice in this posting. The result was tedious and unwieldy with ‘tag’ being used to identify which sub-method to call for each new functionality. Low grade regarding code maintenance. 2) c++17 has accepted std::variant. Since that is currently not yet implemented in VS2015 Update 2, I set about using boost::variant. See What is the right c++ variant syntax for calling a member function set to a particular variant? which uses the Visitor pattern to allow access to initialized variant members and member functions. This eliminates the ‘tag’ switches and variant ‘get’ calls. Bottom line: I dropped my class-like union and adopted variant for creating maintainable code that uses initializer_list to store variant member functionality all being initializable at compile time (read: highly maintainable).

解决方案

Alright, I'm feeling generous and I've made custom unions myself so he're some stuff that'll get you set up. I've rewritten your S structure to be more compliant and usable. (I've made changes marked by comments)

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;
  };
};

One of the things that you were doing improperly was skipping the construction and destruction of B and C and going straight for the internal variables. You should always create and destroy types properly even when they may be trivial. While this may work out, not initializing these objects properly is only asking for trouble (It also makes it easier should you change B or C in the future).

To make using the class easier, I added in the proper constructors for std::string and int as well as an assignment operator. Because now that we can construct the objects how we want, your main() could look like this:

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;
}

I encourage you to modify B and C to use constructors to build their internals rather than relying on S.

这篇关于在std :: initializer_list中使用类似union的类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆