C ++ 구조 초기화


279

아래 표시된 것처럼 C ++에서 구조체를 초기화 할 수 있습니까?

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city = "Hamilton", .prov = "Ontario" };

여기여기 의 링크 는 C에서만이 스타일을 사용할 수 있다고 언급합니다. 그렇다면 C ++에서는 왜 이것이 불가능합니까? C ++로 구현되지 않은 근본적인 기술적 이유가 있거나이 스타일을 사용하는 것은 좋지 않습니다. 내 구조체가 크고이 스타일을 사용하면 어떤 멤버에 어떤 값이 할당되었는지 명확하게 읽을 수 있기 때문에이 초기화 방법을 사용하는 것이 좋습니다.

동일한 가독성을 얻을 수있는 다른 방법이 있다면 저에게 알려주십시오.

이 질문을 게시하기 전에 다음 링크를 참조했습니다

  1. AIX 용 C / C ++
  2. 변수를 사용한 C 구조 초기화
  3. C ++에서 태그를 사용한 정적 구조 초기화
  4. C ++ 11 적절한 구조 초기화

20
세계에 대한 개인적인 견해 : 대신 생성자를 사용해야하므로 C ++에서는 이러한 스타일의 객체 초기화가 필요하지 않습니다.
Philip Kendall

7
그렇습니다.하지만 큰 구조의 배열이 있습니다. 이 방법을 사용하는 것은 쉽고 읽기 쉽습니다. 더 나은 가독성을 제공하는 Constructor를 사용하여 초기화하는 스타일 / 모범 사례가 있습니까?
Dinesh PR

2
생성자와 함께 부스트 매개 변수 라이브러리를 고려 했습니까? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy

18
프로그래밍과 관련이 없습니다.이 주소는 미국에서만 잘 작동합니다. 프랑스에는 '지방'이 없습니다. 전 세계 다른 지역에는 우편 번호가 없습니다. 친구의 조부모는 작은 마을에 거주하며 주소는 "Ms X, 우편 번호입니다" 작은 마을 이름 "(예, 거리 없음). 따라서 적용 할 시장에 유효한 주소가 무엇인지 신중히 고려하십시오.;)
Matthieu M.

5
@MatthieuM. 미국에는 지방이 없지만 (캐나다 형식 일 수 있습니까?), 거리를 언급하지 않는 주, 영토 및 작은 마을도 있습니다. 따라서 주소 적합성 문제는 여기에서도 적용됩니다.
Tim

답변:


166

각 이니셜 라이저 값이 무엇인지 명확히하려면 각 줄에 주석을 달아 여러 줄로 나누십시오.

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};

7
나는 개인적으로이 스타일을 좋아하고 추천합니다
Dinesh PR

30
그렇게하는 것과 실제로 도트 표기법을 사용하여 필드 자체에 더 정확하게 액세스하는 것의 차이점은 문제가되는 경우 공간을 절약하는 것과는 다릅니다. 일관성 있고 유지 관리 가능한 코드를 작성할 때 C ++ 프로그래머에게는 도움이되지 않습니다. 코드를 돋보이게하기 위해 항상 다른 무언가를 원합니다. 코드는 해결해서는 안되는 문제를 반영하기위한 것입니다. 자체 관용구, 신뢰성 및 유지 보수 용이성을 목표로합니다.

5
@ user1043000 하나,이 경우에는 회원을 등록하는 순서가 가장 중요합니다. 구조 중간에 필드를 추가하는 경우이 코드로 돌아가서 새 초기화를 삽입 할 정확한 지점을 찾아야합니다. 이는 어렵고 지루합니다. 점 표기법을 사용하면 순서를 방해하지 않고 목록 끝에 새 초기화를 간단히 넣을 수 있습니다. char*구조에서 위 또는 아래의 다른 멤버 중 하나 와 같은 유형 (예 :)을 추가 할 경우 점 표기법이 훨씬 안전 합니다. 스왑의 위험이 없기 때문입니다.
Gui13

6
orip의 의견. 데이터 구조 정의가 변경되어서 아무도 초기화를 찾지 못하거나 모두를 찾을 수 없거나 편집을 실수로 잘못하면 문제가 발생합니다.
Edward Falk

3
대부분의 POSIX 구조체에는 정의 된 순서가없고 정의 된 멤버 만 있습니다. (struct timeval){ .seconds = 0, .microseconds = 100 }항상 백 마이크로 초이지만 timeval { 0, 100 }SECONDS 일 수 있습니다 . 당신은 그런 식으로 어려운 것을 찾기를 원하지 않습니다.
yyny

99

내 질문 : (C ++의 구조에 대한 태그 기반 초기화를 구현하지 않기 때문에) 더 만족하는 결과를 초래, 내가 여기에있는 트릭 갔다 는 C ++ 구조체의 멤버는 기본적으로 0으로 초기화를?

당신을 위해 그것은 그렇게 할 것입니다 :

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

이것은 원래 원하는 것과 가장 가깝습니다 (초기화하려는 필드를 제외한 모든 필드는 0입니다).


10
정적으로
inintialized

5
static address temp_address = {};작동합니다. 나중에 채우는 것은 런타임에 달려 있습니다. 초기화를 수행하는 정적 함수를 제공하여이를 무시할 수 있습니다 static address temp_address = init_my_temp_address();..
Gui13

C ++ 11에서 init_my_temp_address람다 함수가 될 수 있습니다.static address temp_address = [] () { /* initialization code */ }();
dureuill

3
나쁜 생각, 그것은 RAII 원칙을 위반합니다.
hxpax

2
정말 나쁜 생각 : 하나의 멤버를 추가 address하면 address새 멤버 를 생성하고 초기화하지 않는 모든 장소를 알 수 없습니다.
mystery_doctor


17

필드 식별자는 실제로 C 이니셜 라이저 구문입니다. C ++에서는 필드 이름없이 올바른 순서로 값을 제공하십시오. 불행히도 이것은 당신이 그들 모두를 제공해야 함을 의미합니다 (실제로 값이 0 인 필드를 생략하고 결과는 동일합니다) :

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 

1
예, 항상 정렬 된 구조체 초기화를 사용할 수 있습니다.
texasbruce

3
예, 현재이 방법 만 사용하고 있습니다 (정렬 된 구조 초기화). 그러나 가독성이 좋지 않다고 생각합니다. 내 구조가 크기 때문에 이니셜 라이저에는 많은 데이터가 있으며 어떤 값이 어떤 멤버에 할당되었는지 추적하기가 어렵습니다.
Dinesh PR

7
Di 그런 다음 생성자를 작성하십시오!
Mr Lister

2
@MrLister (또는 누군가) 아마도 나는 어리 석음의 구름에 갇혀 있었지만 생성자가 훨씬 더 나은 방법을 설명하려고 관심이 있습니까? 이니셜 라이저 목록에 순서 종속 이름이없는 값을 제공하는 것과 생성자에 순서 종속 이름이없는 값을 제공하는 것에는 약간의 차이가있는 것 같습니다 ...?
yano

1
@yano 솔직히 말해서, 왜 생성자가 문제에 대한 답이라고 생각했는지 기억이 나지 않습니다. 기억이 나면 다시 돌아올께
Mr Lister

13

이 기능을 지정된 이니셜 라이저 라고 합니다. C99 표준에 추가되었습니다. 그러나이 기능은 C ++ 11에서 제외되었습니다. C ++ 프로그래밍 언어, 제 4 판, 섹션 44.3.3.2 (C ++에서 채택하지 않은 C 기능)에 따르면 :

C99 (C89와 비교하여)에 대한 몇 가지 추가 사항은 C ++에서 의도적으로 채택되지 않았습니다.

가변 길이 어레이 (VLA); 벡터 또는 어떤 형태의 동적 배열 사용

[2] 지정된 초기화 기; 생성자 사용

C99 문법에는 지정된 이니셜 라이저가 있습니다 [ISO / IEC 9899 : 2011, N1570위원회 초안-2011 년 4 월 12 일 참조]

6.7.9 초기화

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designation_opt initializer
    initializer-list , designationopt initializer
designation:
    designator-list =
designator-list:
    designator
    designator-list designator
designator:
    [ constant-expression ]
    . identifier

한편, C ++ 11에는 지정된 초기화 기가 없습니다 [ISO / IEC 14882 : 2011, N3690위원회 초안-2013 년 5 월 15 일 참조]

8.5 이니셜 라이저

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

동일한 효과를 얻으려면 생성자 또는 이니셜 라이저 목록을 사용하십시오.


9

ctor를 통해 초기화 할 수 있습니다.

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};

9
의 정의를 제어하는 ​​경우에만 해당됩니다 struct address. 또한 POD 유형에는 의도적으로 생성자와 소멸자가 없습니다.
user4815162342

7

Gui13의 솔루션을 단일 초기화 명령문으로 묶을 수도 있습니다.

struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };


address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

면책 조항 : 나는이 스타일을 권장하지 않습니다


멤버를 추가 할 수 있기 때문에 여전히 위험 address합니다. 코드는 원래 5 개의 멤버 만 초기화하는 백만 개의 장소로 컴파일됩니다. 구조체 초기화의 가장 좋은 부분은 모든 멤버를 가질 수 있고 모든 멤버 const를 강제로 초기화한다는 것입니다.
mystery_doctor

7

나는이 질문이 꽤 오래되었다는 것을 알고 있지만 constexpr과 currying을 사용하여 초기화하는 또 다른 방법을 찾았습니다.

struct mp_struct_t {
    public:
        constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
        constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
        constexpr mp_struct_t another_member(int member) { return {member1, member,     member2}; }
        constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

    int member1, member2, member3;
};

static mp_struct_t a_struct = mp_struct_t{1}
                           .another_member(2)
                           .yet_another_one(3);

이 방법은 전역 정적 변수 및 constexpr 변수에도 작동합니다. 유일한 단점은 유지 관리 성이 나쁘다는 것입니다.이 방법을 사용하여 다른 멤버를 초기화 할 수있을 때마다 모든 멤버 초기화 방법을 변경해야합니다.


1
이것은 빌더 패턴 입니다. 멤버 메소드는 매번 새로운 구조체를 생성하는 대신 수정 될 프로퍼티에 대한 참조를 반환 할 수 있습니다
phuclv

6

왜 여기에 뭔가 빠져있을 수 있습니다.

#include <cstdio>    
struct Group {
    int x;
    int y;
    const char* s;
};

int main() 
{  
  Group group {
    .x = 1, 
    .y = 2, 
    .s = "Hello it works"
  };
  printf("%d, %d, %s", group.x, group.y, group.s);
}

위의 프로그램을 MinGW C ++ 컴파일러와 Arduino AVR C ++ 컴파일러로 컴파일했으며 둘 다 예상대로 실행되었습니다. #include <cstdio>
run_the_race

4
@run_the_race, 이것은 주어진 컴파일러의 행동이 아닌 C ++ 표준이 말하는 것에 관한 것입니다. 그러나이 기능은 c ++ 20에 제공됩니다.
Jon

구조체가 POD 인 경우에만 작동합니다. 따라서 생성자를 추가하면 컴파일이 중지됩니다.
oromoiluig

5

C ++로 구현되지 않았습니다. (또한 char*문자열입니까? 나는 희망하지 않습니다).

일반적으로 매개 변수가 너무 많으면 코드 냄새가 심각합니다. 그러나 왜 단순히 구조체를 가치 초기화하고 각 멤버를 할당하지 않습니까?


6
"(또한 char*문자열? 희망하지 않습니다)." -C 예입니다.
Ed S.

C ++에서 char *를 사용할 수 없습니까? 현재 사용하고 있으며 작동하고 있습니다 (잘못된 일을하고있을 수 있습니다). 컴파일러는 "Hamilton"& "Ontario"의 상수 문자열을 생성하고 해당 주소를 구조체 멤버에 할당한다고 가정합니다. 대신 const char *를 사용하는 것이 맞습니까?
Dinesh PR

8
사용할 수는 char*있지만 const char*훨씬 안전 std::string합니다. 훨씬 안정적이기 때문에 모두가 사용합니다 .
강아지

확인. "아래에 언급 한대로"를 읽을 때 어딘가에서 복사 된 예라고 가정했습니다.
Ed S.

2

원래 구조 정의를 수정할 필요가없는 전역 변수에 대해이 방법을 찾았습니다.

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

그런 다음 원래 구조체 형식에서 상속 된 새 형식의 변수를 선언하고 필드 초기화에 생성자를 사용합니다.

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

C 스타일만큼 우아하지는 않지만 ...

로컬 변수의 경우 생성자의 시작 부분에 추가 memset (this, 0, sizeof (* this))이 필요하므로 분명히 나쁘지 않으며 @ gui13의 대답이 더 적합합니다.

( 'temp_address'는 'temp_address'유형의 변수이지만이 새로운 유형은 'address'에서 상속되며 'address'가 필요한 모든 장소에서 사용할 수 있으므로 괜찮습니다.)


1

나는 오늘 비슷한 문제에 직면했는데, 테스트 할 함수에 인수로 전달 될 테스트 데이터로 채우려는 구조체가 있습니다. 나는이 구조체들의 벡터를 갖고 싶었고 각 구조체를 초기화하는 하나의 라이너 방법을 찾고있었습니다.

나는 구조체에서 생성자 함수를 사용하여 결국에는 귀하의 질문에 대한 몇 가지 답변으로 제안되었다고 생각합니다.

생성자에 대한 인수가 공용 멤버 변수와 동일한 이름을 가지며 this포인터를 사용해야하는 것은 좋지 않습니다 . 더 좋은 방법이 있다면 누군가 편집을 제안 할 수 있습니다.

typedef struct testdatum_s {
    public:
    std::string argument1;
    std::string argument2;
    std::string argument3;
    std::string argument4;
    int count;

    testdatum_s (
        std::string argument1,
        std::string argument2,
        std::string argument3,
        std::string argument4,
        int count)
    {
        this->rotation = argument1;
        this->tstamp = argument2;
        this->auth = argument3;
        this->answer = argument4;
        this->count = count;
    }

} testdatum;

테스트 함수에서 다음과 같이 다양한 인수로 테스트중인 함수를 호출하는 데 사용했습니다.

std::vector<testdatum> testdata;

testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));

for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
    function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}

1

가능하지만 초기화하는 구조체가 POD (plain old data) 구조체 인 경우에만 가능합니다. 메소드, 생성자 또는 기본값을 포함 할 수 없습니다.


1

C ++에서 C 스타일 이니셜 라이저는 생성자로 대체되어 컴파일 시간에 따라 유효한 초기화 만 수행되도록 할 수 있습니다 (초기화 후 객체 멤버가 일관됨).

모범 사례이지만 때로는 예와 같이 사전 초기화가 편리합니다. OOP는이를 추상 클래스 또는 창작 디자인 패턴으로 해결합니다 .

내 생각에,이 안전한 방법을 사용하면 단순함이 사라지고 간결한 코드는 유지 관리하기 위해 정교한 디자인이 필요하지 않기 때문에 때로는 보안 상충 관계가 너무 비쌀 수 있습니다.

대안 솔루션으로, 람다를 사용하여 매크로를 정의하여 C 스타일처럼 보이도록 초기화를 단순화하는 것이 좋습니다.

struct address {
  int street_no;
  const char *street_name;
  const char *city;
  const char *prov;
  const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

ADDRESS 매크로가

[] { address _={}; /* definition... */ ; return _; }()

람다를 생성하고 호출합니다. 매크로 매개 변수도 쉼표로 구분되므로 이니셜 라이저를 대괄호로 묶고 다음과 같이 호출해야합니다.

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

일반화 된 매크로 이니셜 라이저를 작성할 수도 있습니다.

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

그러나 전화는 약간 덜 아름답습니다

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

그러나 일반 INIT 매크로를 사용하여 ADDRESS 매크로를 쉽게 정의 할 수 있습니다.

#define ADDRESS(x) INIT(address,x)


0

이 깔끔한 답변에서 영감을 얻었습니다 : ( https://stackoverflow.com/a/49572324/4808079 )

lamba 클로저를 할 수 있습니다 :

// Nobody wants to remember the order of these things
struct SomeBigStruct {
  int min = 1;
  int mean = 3 ;
  int mode = 5;
  int max = 10;
  string name;
  string nickname;
  ... // the list goes on
}

.

class SomeClass {
  static const inline SomeBigStruct voiceAmps = []{
    ModulationTarget $ {};
    $.min = 0;  
    $.nickname = "Bobby";
    $.bloodtype = "O-";
    return $;
  }();
}

아니면, 당신이 매우 공상하고 싶다면

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

class SomeClass {
  static const inline SomeBigStruct voiceAmps = DesignatedInit(
    ModulationTarget,
    $.min = 0,
    $.nickname = "Bobby",
    $.bloodtype = "O-",
  );
}

초기화되지 않은 멤버와 관련이있는 몇 가지 단점이 있습니다. 링크 된 답변 의견에서 말한 것처럼 테스트하지는 않았지만 효율적으로 컴파일됩니다.

전반적으로, 나는 그것이 깔끔한 접근법이라고 생각합니다.

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