方便的C ++ struct初始化

时间:2011-05-30 23:59:55

标签: c++ struct initialization

我正在尝试找到一种方便的方法来初始化'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 */或更好的类似的东西? 或者,我可以解释为什么我不想这样做(即为什么我的心理范式很糟糕)。

修改

通过方便,我的意思是可维护非冗余

13 个答案:

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

officialy supported

答案 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一起使用。但另一方面,它是干净,简单,纯净和标准的。