【发布时间】: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复制构造函数真正起作用。它需要通过复制构造来构造B或C,具体取决于s.tag是什么。还要根据tag使您的析构函数销毁正确的类型。您应该避免在课堂外手动调用new和析构函数。将其中一种类型设为默认构造函数中的默认类型 -
复制构造函数已更新。
标签: c++11 unions initializer-list