方便的 C++ 结构初始化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6181715/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Convenient C++ struct initialisation
提问by bitmask
I'm trying to find a convenient way to initialise 'pod' C++ structs. Now, consider the following struct:
我试图找到一种方便的方法来初始化“pod”C++ 结构。现在,考虑以下结构:
struct FooBar {
int foo;
float bar;
};
// just to make all examples work in C and C++:
typedef struct FooBar FooBar;
If I want to conveniently initialise this in C (!), I could simply write:
如果我想在 C (!) 中方便地初始化它,我可以简单地写:
/* A */ FooBar fb = { .foo = 12, .bar = 3.4 }; // illegal C++, legal C
Note that I want to explicitly avoid the following notation, because it strikes me as being made to break my neck if I change anythingin the struct in the future:
请注意,我想明确避免使用以下符号,因为如果我将来更改结构中的任何内容,我觉得它会折断我的脖子:
/* B */ FooBar fb = { 12, 3.4 }; // legal C++, legal C, bad style?
To achieve the same (or at least similar) in C++ as in the /* A */
example, I would have to implement an idiotic constructor:
为了在 C++ 中实现与/* A */
示例中相同(或至少相似),我必须实现一个愚蠢的构造函数:
FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) {}
// ->
/* C */ FooBar fb(12, 3.4);
Which is good for boiling water, but not suitable for lazy people (laziness is a good thing, right?). Also, it is pretty much as bad as the /* B */
example, as it does not explicitly state which value goes to which member.
哪个适合烧水,但不适合懒人(懒惰是好事,对吧?)。此外,它与/* B */
示例一样糟糕,因为它没有明确说明哪个值属于哪个成员。
So, my question is basically how I can achieve something similar to /* A */
or better in C++?
Alternatively, I would be okay with an explanation why I should not want to do this (i.e. why my mental paradigm is bad).
所以,我的问题基本上是如何/* A */
在 C++ 中实现类似或更好的东西?或者,我可以解释为什么我不应该这样做(即为什么我的心理范式很糟糕)。
EDIT
编辑
By convenient, I mean also maintainableand non-redundant.
通过方便的,我的意思也是维护和非冗余。
采纳答案by ivaigult
Designated initializes will be supported in c++2a, but you don't have to wait, because they are officialy supportedby GCC, Clang and MSVC.
c++2a 将支持指定的初始化,但您不必等待,因为GCC、Clang 和 MSVC已正式支持它们。
#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;
}
回答by iammilind
Since style A
is not allowed in C++ and you don't want style B
then how about using style BX
:
因为style A
在 C++ 中是不允许的,而且你不想要,style B
那么如何使用style BX
:
FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 }; // :)
At least help at some extent.
至少在某种程度上有所帮助。
回答by eyelash
You could use a lambda:
您可以使用 lambda:
const FooBar fb = [&] {
FooBar fb;
fb.foo = 12;
fb.bar = 3.4;
return fb;
}();
More information on this idiom can be found on Herb Sutter's blog.
关于这个习语的更多信息可以在Herb Sutter 的博客上找到。
回答by ralphtheninja
Extract the contants into functions that describe them (basic refactoring):
将常量提取到描述它们的函数中(基本重构):
FooBar fb = { foo(), bar() };
I know that style is very close to the one you didn't want to use, but it enables easier replacement of the constant values and also explain them (thus not needing to edit comments), if they ever change that is.
我知道这种风格与您不想使用的风格非常接近,但它可以更轻松地替换常量值并解释它们(因此不需要编辑注释),如果它们改变了。
Another thing you could do (since you are lazy) is to make the constructor inline, so you don't have to type as much (removing "Foobar::" and time spent switching between h and cpp file):
您可以做的另一件事(因为您很懒惰)是使构造函数内联,因此您不必键入太多内容(删除“Foobar::”以及在 h 和 cpp 文件之间切换所花费的时间):
struct FooBar {
FooBar(int f, float b) : foo(f), bar(b) {}
int foo;
float bar;
};
回答by Matthieu M.
Your question is somewhat difficult because even the function:
你的问题有点困难,因为即使是函数:
static FooBar MakeFooBar(int foo, float bar);
may be called as:
可以称为:
FooBar fb = MakeFooBar(3.4, 5);
because of the promotion and conversions rules for built-in numeric types. (C has never been really strongly typed)
因为内置数字类型的提升和转换规则。(C 从来没有真正的强类型)
In C++, what you want is achievable, though with the help of templates and static assertions:
在 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 };
}
In C, you may name the parameters, but you'll never get further.
在 C 中,您可以命名参数,但您将永远无法获得更多。
On the other hand, if all you want is named parameters, then you write a lot of cumbersome code:
另一方面,如果你想要的只是命名参数,那么你会写很多繁琐的代码:
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);
And you can pepper in type promotion protection if you like.
如果你愿意,你可以加入类型提升保护。
回答by Matthias Urlichs
Many compilers' C++ frontends (including GCC and clang) understand C initializer syntax. If you can, simply use that method.
许多编译器的 C++ 前端(包括 GCC 和 clang)都理解 C 初始值设定项语法。如果可以,只需使用该方法。
回答by parapura rajkumar
Yet another way in C++ is
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);
回答by John
Option D:
选项 D:
FooBar FooBarMake(int foo, float bar)
FooBar FooBarMake(int foo, float bar)
Legal C, legal C++. Easily optimizable for PODs. Of course there are no named arguments, but this is like all C++. If you want named arguments, Objective C should be better choice.
合法的 C,合法的 C++。可轻松针对 POD 进行优化。当然没有命名参数,但这就像所有 C++ 一样。如果你想要命名参数,Objective C 应该是更好的选择。
Option E:
选项 E:
FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;
Legal C, legal C++. Named arguments.
合法的 C,合法的 C++。命名参数。
回答by Max Vollmer
I know this question is old, but there is a way to solve this until C++20 finally brings this feature from C to C++. What you can do to solve this is use preprocessor macros with static_asserts to check your initialization is valid. (I know macros are generally bad, but here I don't see another way.) See example code below:
我知道这个问题很老,但是在 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...
Then when you have a struct with const attributes, you can do this:
然后当你有一个具有 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.);
It's a bit inconvenient, because you need macros for every possible number of attributes and you need to repeat the type and name of your instance in the macro call. Also you cannot use the macro in a return statement, because the asserts come after the initialization.
这有点不方便,因为您需要为每个可能的属性数量使用宏,并且需要在宏调用中重复实例的类型和名称。您也不能在 return 语句中使用宏,因为断言是在初始化之后出现的。
But it does solve your problem: When you change the struct, the call will fail at compile-time.
但它确实解决了您的问题:当您更改结构时,调用将在编译时失败。
If you use C++17, you can even make these macros more strict by forcing the same types, e.g.:
如果您使用 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);\
回答by ?? Tiib
The way /* B */
is fine in C++ also the C++0x is going to extend the syntax so it is useful for C++ containers too. I do not understand why you call it bad style?
这种方式/* B */
在 C++ 中很好,C++0x 也将扩展语法,因此它对 C++ 容器也很有用。我不明白你为什么称之为坏风格?
If you want to indicate parameters with names then you can use boost parameter library, but it may confuse someone unfamiliar with it.
如果你想用名字来表示参数,那么你可以使用boost parameter library,但它可能会让不熟悉它的人感到困惑。
Reordering struct members is like reordering function parameters, such refactoring may cause problems if you don't do it very carefully.
对结构体成员进行重新排序就像对函数参数进行重新排序一样,如果不非常小心地进行重构,可能会出现问题。