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


141

'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 ++에서 하거나 더 나은 . 또는 왜 이렇게하지 말아야하는지 (즉, 내 정신 패러다임이 나쁜 이유) 설명을해도 괜찮을 것입니다.

편집하다

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


2
나는 B 예제가 당신이 얻는 것만 큼 가깝다고 생각합니다.
Marlon

2
예제 B가 "나쁜 스타일"인 방법을 모르겠습니다. 각 멤버를 각각의 값으로 차례로 초기화하기 때문에 나에게 의미가 있습니다.
Mike Bailey

26
마이크, 어떤 가치가 어느 멤버에게 어떤 영향을 미치는지 명확하지 않기 때문에 나쁜 스타일입니다. 구조체의 정의를보고 멤버를 세어 각 값의 의미를 찾아야합니다.
jnnnnn

9
또한 FooBar의 정의가 나중에 변경 될 경우 초기화가 중단 될 수 있습니다.
Edward Falk

초기화가 길고 복잡해지면 빌더 패턴을 잊지 마십시오
sled

답변:


20

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


주의 사항 : 나중에 구조체 끝에 매개 변수를 추가하면 이전 초기화는 초기화되지 않고 자동으로 컴파일됩니다.
Catskul

1
@Catskul 번호는 그것은 초기화됩니다 0으로 초기화에 발생합니다 빈 초기화 목록과 함께.
ivaigult

네가 옳아. 감사합니다. 나머지 매개 변수는 자동으로 효과적으로 기본 초기화됩니다. 필자가 의도 한 것은 POD 유형을 완전히 명시 적으로 초기화하는 데 도움이되기를 희망하는 사람은 실망 할 것입니다.
Catskul

43

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

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

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


8
+1 : (컴파일러 POV에서) 올바른 초기화를 보장하지는 않지만 주석을 동기화해야하지만 독자에게 도움이됩니다.
Matthieu M.

18
코멘트 내가 사이에 새 필드를 삽입 할 경우 파손되는 구조의 초기화를 방지하지 않습니다 foobar미래에. C는 여전히 원하는 필드를 초기화하지만 C ++은 초기화하지 않습니다. 그리고 이것이 문제의 핵심입니다-C ++에서 동일한 결과를 얻는 방법. 내 말은, 파이썬은 명명 된 인수, "명명 된"필드와 함께 C를 사용하고 C ++도 무언가를 가져야한다.
dmitry_romanov

2
댓글이 동기화되어 있습니까? 휴식 좀 줘 안전이 창을 통과합니다. 파라미터와 붐을 다시 주문하십시오. 로 훨씬 좋습니다 explicit FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) . 명시 적 키워드에 유의하십시오 . 안전과 관련하여 표준을 위반하는 것이 더 좋습니다. Clang에서 : -Wno-
Daniel O

@DanielW, 더 나은지 아닌지에 관한 것이 아닙니다. OP가 유효한 모든 경우를 다루는 스타일 A (c ++가 아닌), B 또는 C를 원하지 않는 것에 따라이 대답.
iammilind

@iammilind OP의 정신 패러다임이 나쁜 이유에 대한 힌트 가 대답을 향상시킬 수 있다고 생각합니다 . 나는 이것을 지금처럼 위험하다고 생각합니다.
데스트

10

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

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

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


1
이러한 접근 방식은 필드를 두 번 초기화합니다. 생성자에 한 번. 두 번째는 fb.XXX = YYY입니다.
Dmytro Ovdiienko

9

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

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

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

당신이 할 수있는 또 다른 일은 (게으 르기 때문에) 생성자를 인라인으로 만드는 것이므로 많이 입력 할 필요가 없습니다 ( "Foobar ::"및 h와 cpp 파일 간 전환에 걸린 시간 제거) :

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

1
내가 원하는 모든 것이 일련의 값으로 구조체를 빠르게 초기화 할 수 있다면이 질문에 대한 다른 사람 이이 답변에 대한 하단 코드 스 니펫에서 스타일을 선택하는 것이 좋습니다.
kayleeFrye_onDeck

8

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

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

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


1
"C ++에서는 원하는 것을 달성 할 수 있습니다": OP가 매개 변수 순서의 혼합을 방지하도록 요청하지 않았습니까? 제안한 템플릿으로 어떻게 달성 할 수 있습니까? 간단히하기 위해 두 개의 매개 변수가 모두 있다고 가정 해 봅시다.
최대

@ max : OP 상황 인 유형이 다른 경우 (서로 변환 가능하더라도) 방지합니다. 유형을 구별 할 수 없다면 물론 작동하지 않지만 완전히 다른 질문입니다.
Matthieu M.

아 알았다. 예, 이들은 두 가지 다른 문제이며 현재 두 번째 문제는 C ++에서 좋은 솔루션을 가지고 있지 않다고 생각합니다 (그러나 C ++ 20은 집계 초기화에서 C99 스타일 매개 변수 이름에 대한 지원을 추가하는 것으로 보입니다).
최대

6

많은 컴파일러의 C ++ 프론트 엔드 (GCC 및 clang 포함)는 C 이니셜 라이저 구문을 이해합니다. 가능하면 해당 방법을 사용하십시오.


16
C ++ 표준을 준수하지 않는 것!
비트 마스크

5
나는 그것이 비표준이라는 것을 알고 있습니다. 그러나 그것을 사용할 수 있다면 여전히 구조체를 초기화하는 가장 현명한 방법입니다.
Matthias Urlichs

2
잘못된 생성자를 비공개로 만드는 x 및 y 유형을 보호 할 수 있습니다.private: FooBar(float x, int y) {};
dmitry_romanov

4
clang (llvm 기반 c ++ 컴파일러)도이 구문을 지원합니다. 그것은 표준의 일부가 아닙니다.
nimrodm

우리는 모두 C 이니셜 라이저가 C ++ 표준의 일부가 아니라는 것을 알고 있습니다. 그러나 많은 컴파일러가 그것을 이해하고 있으며 어떤 컴파일러가 목표로하고 있는지에 대한 질문은 없었습니다. 따라서이 답변을 공감하지 마십시오.
Matthias Urlichs

4

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

2
함수형 프로그래밍에는 번거롭지 만 (즉, 함수 호출의 인수 목록에서 객체 생성) 실제로는 그렇지 않으면 깔끔한 아이디어입니다!
비트 마스크

27
옵티마이 저는 아마 그것을 줄이지 만 내 눈은 그렇지 않습니다.
Matthieu M.

6
두 단어 : argh ... argh! 'Point pt; pt.x = pt.y = 20;`? 또는 캡슐화를 원한다면 생성자보다 어떻게 더 낫습니까?
OldPeculier 2016 년

3
매개 변수 순서에 대한 생성자 선언을 살펴 봐야하므로 생성자보다 낫습니다. x, y 또는 y, x
입니까

2
const 구조체를 원하면 작동하지 않습니다. 또는 초기화되지 않은 구조체를 허용하지 않도록 컴파일러에 지시하려는 경우. 당신이 경우 정말 이런 식으로하고 싶지, 적어도와 세터를 표시 inline!
Matthias Urlichs

3

옵션 D :

FooBar FooBarMake(int foo, float bar)

법적 C, 법적 C ++ POD에 쉽게 최적화 할 수 있습니다. 물론 명명 된 인수는 없지만 모든 C ++과 같습니다. 명명 된 인수를 원하면 Objective C가 더 나은 선택입니다.

옵션 E :

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

법적 C, 법적 C ++ 명명 된 인수.


12
FooBar fb = {};C ++에서 사용할 수있는 memset 대신 모든 구조체 멤버가 기본적으로 초기화됩니다.
Öö Tiib

@ ÖöTiib : 불행히도 그것은 불법 C입니다.
CB Bailey

3

나는이 질문이 오래되었다는 것을 알고 있지만 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);\

명명 된 이니셜 라이저를 허용하는 C ++ 20 제안이 있습니까?
Maël Nison


2

방법 /* B */C ++에서는 좋은 이며 C ++ 0x는 구문을 확장하므로 C ++ 컨테이너에도 유용합니다. 나는 왜 당신이 나쁜 스타일이라고 부르는지 이해하지 못합니까?

이름으로 매개 변수를 표시하려면 boost parameter library를 사용할 수 있지만 익숙하지 않은 사람을 혼동시킬 수 있습니다.

구조체 멤버를 재정렬하는 것은 함수 매개 변수를 재정렬하는 것과 같습니다. 리팩토링은 매우 신중하게 수행하지 않으면 문제를 일으킬 수 있습니다.


7
유지 관리가 불가능하다고 생각하기 때문에 나쁜 스타일이라고 부릅니다. 1 년 안에 다른 회원을 추가하면 어떻게됩니까? 또는 멤버의 순서 / 유형을 변경하면 어떻게됩니까? 초기화하는 모든 코드는 (아마도) 깨질 수 있습니다.
비트 마스크

2
@bitmask 그러나 명명 된 인수가 없으면 생성자 호출도 업데이트해야하며 생성자가 유지할 수없는 나쁜 스타일이라고 생각하는 사람은 많지 않습니다. 또한 명명 된 초기화는 C가 아니지만 C99 인 C99는 확실히 슈퍼 세트가 아니라고 생각합니다.
Christian Rau

2
구조체의 끝에 1 년 안에 다른 멤버를 추가하면 기존 코드에서 기본적으로 초기화됩니다. 그것들을 재정렬하면 기존 코드를 모두 편집해야합니다.
Öö Tiib

1
@bitmask : 첫 번째 예제는 "유지 불가능"입니다. 구조체에서 변수의 이름을 바꾸면 어떻게됩니까? 물론 대체를 할 수는 있지만 실수로 이름을 바꾸지 않아야하는 변수의 이름을 바꿀 수 있습니다.
Mike Bailey

@ChristianRau C99는 언제부터 C가 아니십니까? C 그룹과 C99가 특정 버전 / ISO 사양이 아닙니까?
altendky

1

이 구문은 어떻습니까?

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

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

Microsoft Visual C ++ 2015 및 g ++ 6.0.2에서 방금 테스트했습니다. 작동합니다.
변수 이름이 중복되는 것을 피하려면 특정 매크로를 만들 수도 있습니다.


clang++3.5.0-10 -Weverything -std=c++1z은 그것을 확인하는 것 같습니다. 그러나 제대로 보이지 않습니다. 표준에서 이것이 유효한 C ++임을 확인하는 곳을 알고 있습니까?
bitmask

모르겠지만 오래 전에 다른 컴파일러에서 사용했지만 아무런 문제가 없었습니다. 이제 g ++ 4.4.7에서 테스트되었습니다.
cls

5
나는이 일을 생각하지 않습니다. 시도하십시오 ABCD abc = { abc.b = 7, abc.a = 5 };.
raymai97

@deselect, 필드가 값으로 초기화되어 operator =에 의해 반환되므로 작동합니다. 따라서 실제로 클래스 멤버를 두 번 초기화합니다.
Dmytro Ovdiienko

1

나에게 인라인 초기화를 허용하는 가장 게으른 방법은이 매크로를 사용하는 것입니다.

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

이 매크로는 속성 및 자체 참조 방법을 만듭니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.