在 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;
};
};
您做错的一件事是跳过 B
和 C
的构造和破坏,直接进入内部变量。您应该始终正确地创建和销毁类型,即使它们可能微不足道。虽然这可能会奏效,但没有正确初始化这些对象只会带来麻烦(如果您将来更改 B
或 C
也会更容易)。
为了更容易使用 class,我为 std::string
和 int
添加了适当的构造函数以及赋值运算符。因为现在我们可以按照我们想要的方式构造对象,所以您的 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
。
在下面的代码中,我展示了类似联合的 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。请参阅
好吧,我感觉很慷慨,我自己创建了自定义联盟,所以他是一些可以帮助您设置的东西。我已经重写了您的 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
也会更容易)。
为了更容易使用 class,我为 std::string
和 int
添加了适当的构造函数以及赋值运算符。因为现在我们可以按照我们想要的方式构造对象,所以您的 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
。