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.