我正在尝试找到一种方便的方法来初始化'pod'C ++结构。现在,考虑以下结构:
struct FooBar {
int foo;
float bar;
};
// just to make all examples work in C and C++:
typedef struct FooBar FooBar;
如果我想在C(!)中方便地初始化它,我可以简单地写:
/* A */ FooBar fb = { .foo = 12, .bar = 3.4 }; // illegal C++, legal C
请注意,我想明确地避免使用以下表示法,因为如果我将来在结构中更改任何,这会让我感到沮丧:
/* B */ FooBar fb = { 12, 3.4 }; // legal C++, legal C, bad style?
要在C ++中实现与/* A */
示例相同(或至少类似),我必须实现一个愚蠢的构造函数:
FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) {}
// ->
/* C */ FooBar fb(12, 3.4);
哪个适合开水,但不适合懒人(懒惰是好事,对吧?)。此外,它与/* B */
示例一样糟糕,因为它没有明确说明哪个值转到哪个成员。
所以,我的问题基本上是如何在C ++中实现与/* A */
或更好的类似的东西?
或者,我可以解释为什么我不想这样做(即为什么我的心理范式很糟糕)。
修改
通过方便,我的意思是可维护和非冗余。
答案 0 :(得分:38)
由于C ++中不允许使用style A
且您不想style B
,因此如何使用style BX
:
FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 }; // :)
至少在某种程度上有所帮助。
答案 1 :(得分:8)
你可以使用lambda:
const FooBar fb = [&] {
FooBar fb;
fb.foo = 12;
fb.bar = 3.4;
return fb;
}();
有关此成语的更多信息,请访问Herb Sutter's blog。
答案 2 :(得分:7)
你的问题有点困难,因为即使是功能:
static FooBar MakeFooBar(int foo, float bar);
可以被称为:
FooBar fb = MakeFooBar(3.4, 5);
因为内置数字类型的促销和转化规则。 (C从未真正强类型化)
在C ++中,尽管有模板和静态断言的帮助,你想要的是可以实现的:
template <typename Integer, typename Real>
FooBar MakeFooBar(Integer foo, Real bar) {
static_assert(std::is_same<Integer, int>::value, "foo should be of type int");
static_assert(std::is_same<Real, float>::value, "bar should be of type float");
return { foo, bar };
}
在C中,您可以命名参数,但是您永远不会得到更多。
另一方面,如果你想要的只是命名参数,那么你写了很多繁琐的代码:
struct FooBarMaker {
FooBarMaker(int f): _f(f) {}
FooBar Bar(float b) const { return FooBar(_f, b); }
int _f;
};
static FooBarMaker Foo(int f) { return FooBarMaker(f); }
// Usage
FooBar fb = Foo(5).Bar(3.4);
如果你愿意,你可以在类型促销保护中加油。
答案 3 :(得分:7)
在c ++ 2a中将支持指定的初始化,但是您不必等待,因为它们在Clang的GCC中是docs。
#include <iostream>
#include <filesystem>
struct hello_world {
const char* hello;
const char* world;
};
int main ()
{
hello_world hw = {
.hello = "hello, ",
.world = "world!"
};
std::cout << hw.hello << hw.world << std::endl;
return 0;
}
答案 4 :(得分:5)
将内容提取到描述它们的函数中(基本重构):
FooBar fb = { foo(), bar() };
我知道风格非常接近你不想使用的风格,但它可以更容易地替换常量值并解释它们(因此不需要编辑注释),如果它们改变了那样。
你可以做的另一件事(因为你很懒)是使构造函数内联,所以你不必输入那么多(删除“Foobar ::”和在h和cpp文件之间切换的时间): / p>
struct FooBar {
FooBar(int f, float b) : foo(f), bar(b) {}
int foo;
float bar;
};
答案 5 :(得分:5)
许多编译器的C ++前端(包括GCC和clang)都了解C初始化器语法。如果可以,只需使用该方法即可。
答案 6 :(得分:2)
C ++的另一种方式是
struct Point
{
private:
int x;
int y;
public:
Point& setX(int xIn) { x = Xin; return *this;}
Point& setY(int yIn) { y = Yin; return *this;}
}
Point pt;
pt.setX(20).setY(20);
答案 7 :(得分:2)
选项D:
FooBar FooBarMake(int foo, float bar)
Legal C,legal C ++。可轻松优化POD。当然没有命名参数,但这就像所有C ++一样。如果你想要命名参数,Objective C应该是更好的选择。
选项E:
FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;
Legal C,legal C ++。命名参数。
答案 8 :(得分:2)
我知道这个问题很老,但有一种方法可以解决这个问题,直到C ++ 20最终将这个功能从C转换为C ++。你可以做的就是使用带有static_asserts的预处理器宏来检查你的初始化是否有效。 (我知道宏通常很糟糕,但在这里我看不到另一种方式。)请参阅下面的示例代码:
#define INVALID_STRUCT_ERROR "Instantiation of struct failed: Type, order or number of attributes is wrong."
#define CREATE_STRUCT_1(type, identifier, m_1, p_1) \
{ p_1 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
#define CREATE_STRUCT_2(type, identifier, m_1, p_1, m_2, p_2) \
{ p_1, p_2 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
#define CREATE_STRUCT_4(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3, m_4, p_4) \
{ p_1, p_2, p_3, p_4 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_4) >= (offsetof(type, m_3) + sizeof(identifier.m_3)), INVALID_STRUCT_ERROR);\
// Create more macros for structs with more attributes...
然后当你有一个带有const属性的结构时,你可以这样做:
struct MyStruct
{
const int attr1;
const float attr2;
const double attr3;
};
const MyStruct test = CREATE_STRUCT_3(MyStruct, test, attr1, 1, attr2, 2.f, attr3, 3.);
这有点不方便,因为您需要每个可能数量的属性的宏,并且您需要在宏调用中重复实例的类型和名称。你也不能在return语句中使用宏,因为断言是在初始化之后发生的。
但它确实解决了您的问题:当您更改结构时,调用将在编译时失败。
如果您使用C ++ 17,您甚至可以通过强制使用相同的类型来使这些宏更严格,例如:
#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_1) == typeid(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_2) == typeid(identifier.m_2), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_3) == typeid(identifier.m_3), INVALID_STRUCT_ERROR);\
答案 9 :(得分:1)
C ++中的/* B */
方式也很好C ++ 0x将扩展语法,因此它对C ++容器也很有用。我不明白你为什么称它为坏风格?
如果你想用名字来表示参数,那么你可以使用boost parameter library,但它可能会让不熟悉它的人感到困惑。
重新排序结构成员就像重新排序函数参数一样,如果不仔细地进行重构,重构可能会导致问题。
答案 10 :(得分:0)
这种语法怎么样?
typedef struct
{
int a;
short b;
}
ABCD;
ABCD abc = { abc.a = 5, abc.b = 7 };
刚刚在Microsoft Visual C ++ 2015和g ++ 6.0.2上测试过。工作正常。
如果要避免重复变量名,也可以创建特定的宏。
答案 11 :(得分:0)
对我来说,允许内联inizialization的最懒的方法是使用此宏。
#define METHOD_MEMBER(TYPE, NAME, CLASS) \
CLASS &set_ ## NAME(const TYPE &_val) { NAME = _val; return *this; } \
TYPE NAME;
struct foo {
METHOD_MEMBER(string, attr1, foo)
METHOD_MEMBER(int, attr2, foo)
METHOD_MEMBER(double, attr3, foo)
};
// inline usage
foo test = foo().set_attr1("hi").set_attr2(22).set_attr3(3.14);
该宏创建属性和自引用方法。
答案 12 :(得分:0)
对于C ++ 20之前的C ++版本(引入命名的初始化,使您的选项A在C ++中有效),请考虑以下事项:
int main()
{
struct TFoo { int val; };
struct TBar { float val; };
struct FooBar {
TFoo foo;
TBar bar;
};
FooBar mystruct = { TFoo{12}, TBar{3.4} };
std::cout << "foo = " << mystruct.foo.val << " bar = " << mystruct.bar.val << std::endl;
}
请注意,如果尝试使用FooBar mystruct = { TFoo{12}, TFoo{3.4} };
初始化结构,则会出现编译错误。
缺点是您必须为主结构中的每个变量创建一个附加结构,并且还必须将内部值与mystruct.foo.val
一起使用。但另一方面,它是干净,简单,纯净和标准的。