development

편리한 C ++ 구조체 초기화

big-blog 2020. 6. 25. 07:28
반응형

편리한 C ++ 구조체 초기화


'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 */어떤 값이 어떤 멤버에게 전달되는지 명시 적으로 명시하지 않기 때문에 예제 만큼이나 나쁩니다 .

그래서 내 질문은 기본적으로 /* A */C ++에서 비슷 하거나 더 나은 것을 얻을 수있는 방법입니다 . 또는 왜 이렇게하지 말아야하는지 (즉, 내 정신 패러다임이 나쁜 이유) 설명을해도 괜찮을 것입니다.

편집하다

하여 편리하게 , 또한 의미 유지 보수비 중복 .


지정된 초기화는 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;
}

GCC Demo MSVC Demo


이후는 style AC에서 허용되지 않습니다 ++ 그리고 당신은 원하지 않는 style B방법을 사용하는 방법에 대한 다음 style BX:

FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 };  // :)

최소한 어느 정도는 도움이됩니다.


기능조차도 귀하의 질문은 다소 어렵습니다.

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);

원하는 경우 유형 홍보 보호에 후추를 넣을 수 있습니다.


람다를 사용할 수 있습니다.

const FooBar fb = [&] {
    FooBar fb;
    fb.foo = 12;
    fb.bar = 3.4;
    return fb;
}();

이 관용구에 대한 자세한 내용은 Herb Sutter의 블로그 에서 찾을 수 있습니다 .


상수를 설명하는 함수로 상수를 추출하십시오 (기본 리팩토링).

FooBar fb = { foo(), bar() };

스타일이 사용하고 싶지 않은 스타일에 매우 가깝다는 것을 알고 있지만 상수 값을 쉽게 대체 할 수 있으며 스타일을 변경하면 설명을 편집 할 필요가 없습니다.

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):

struct FooBar {
  FooBar(int f, float b) : foo(f), bar(b) {}
  int foo;
  float bar;
};

Many compilers' C++ frontends (including GCC and clang) understand C initializer syntax. If you can, simply use that method.


Yet another way in C++ is

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);

Option D:

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.

Option E:

FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;

Legal C, legal C++. Named arguments.


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:

#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:

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.

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.:

#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);\

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?

If you want to indicate parameters with names then you can use boost parameter library, but it may confuse someone unfamiliar with it.

Reordering struct members is like reordering function parameters, such refactoring may cause problems if you don't do it very carefully.


What about this syntax?

typedef struct
{
    int a;
    short b;
}
ABCD;

ABCD abc = { abc.a = 5, abc.b = 7 };

Just tested on a Microsoft Visual C++ 2015 and on g++ 6.0.2. Working OK.
You can make a specific macro also if you want to avoid duplicating variable name.


For me the laziest way to allow inline inizialization is use this macro.

#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);

That macro create attribute and self reference method.

참고URL : https://stackoverflow.com/questions/6181715/convenient-c-struct-initialisation

반응형