【问题标题】:using a union-like class in an std::initializer_list在 std::initializer_list 中使用类联合类
【发布时间】:2016-06-06 20:52:32
【问题描述】:

在下面的代码中,我展示了类联合类 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)根据这篇文章中的建议,我实施了一个完整的类联合解决方案。结果是乏味且笨拙的,使用“标签”来识别为每个新功能调用哪个子方法。代码维护等级低。 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?,它使用访问者模式来允许访问已初始化的变体成员和成员函数。这消除了“标签”开关和变体“获取”调用。底线:我放弃了我的类联合并采用变体来创建可维护的代码,该代码使用 initializer_list 来存储变体成员功能,所有这些功能都可以在编译时初始化(阅读:高度可维护)。

【问题讨论】:

  • 首先,让您的S 复制构造函数真正起作用。它需要通过复制构造来构造BC,具体取决于s.tag 是什么。还要根据tag 使您的析构函数销毁正确的类型。您应该避免在课堂外手动调用new 和析构函数。将其中一种类型设为默认构造函数中的默认类型
  • 复制构造函数已更新。

标签: c++11 unions initializer-list


【解决方案1】:

好吧,我感觉很慷慨,我自己也建立了自定义联盟,所以他是一些可以让你建立起来的东西。我已经重写了您的 S 结构,使其更加合规和可用。 (我已经做了由 cmets 标记的更改)

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,它也会变得更容易)。

为了更轻松地使用该类,我为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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-02
    • 2014-05-08
    • 2022-11-16
    • 2023-03-08
    • 1970-01-01
    • 2012-04-09
    • 2012-11-16
    • 1970-01-01
    相关资源
    最近更新 更多