두 개의 제로 인수 생성자를 구별하는 관용적 방법


41

나는 이런 수업을 가지고있다 :

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    // more stuff

};

일반적 counts으로 그림과 같이 배열 을 기본값으로 초기화하려고합니다 .

그러나 프로파일 링으로 식별 된 선택된 위치에서 배열을 덮어 쓰려고한다는 것을 알고 있기 때문에 배열 초기화를 억제하고 싶지만 컴파일러는 그것을 알아낼만큼 똑똑하지 않습니다.

그러한 "2 차"제로 아거 생성자를 만드는 관용적이고 효율적인 방법은 무엇입니까?

현재 저는 uninit_tag더미 인수로 전달되는 태그 클래스 를 사용하고 있습니다 .

struct uninit_tag{};

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    event_counts(uninit_tag) {}

    // more stuff

};

그런 다음 생성 event_counts c(uninit_tag{});을 억제하고 싶을 때 와 같이 no-init 생성자를 호출합니다 .

나는 더미 클래스의 생성을 포함하지 않거나 어떤 식 으로든 더 효율적인 솔루션에 개방적입니다.


"배열을 덮어 쓰려는 것을 알고 있기 때문에"컴파일러가 이미 최적화를 수행하고 있지 않다고 100 % 확신하십니까? 적절한 예
Frank

6
@ 프랭크-귀하의 질문에 대한 답변이 귀하가 인용 한 문장의 후반부에 있다고 생각하십니까? 그것은 질문에 속하지 않지만 다양한 일이 발생할 수 있습니다. (a) 종종 컴파일러는 죽은 저장소를 제거하기에 충분히 강력하지 않습니다 (b) 때로는 요소의 일부만 덮어 쓰여서 최적화 (그러나 동일한 서브셋 만 나중에 읽음) (c) 때로는 컴파일러 가 그것을 할 수 있지만 메소드가 인라인되지 않기 때문에 예를 들어 패배합니다.
BeeOnRope

수업에 다른 생성자가 있습니까?
NathanOliver 2018

1
@ Frank-어, 당신의 사례는 gcc가 죽은 상점을 제거 하지 않는다는 것을 보여줍니다 . 사실, 당신이 나를 추측하게 만들었다면 gcc 가이 매우 간단한 경우를 얻을 것이라고 생각했을 것입니다. 그러나 여기서 실패하면 약간 더 복잡한 경우를 상상하십시오!
BeeOnRope

1
@uneven_mark-예, gcc 9.2는 -O3에서 수행하지만 (이 최적화는 -O2, IME와 비교할 때 드물지만) 이전 버전에서는 그렇지 않았습니다. 일반적으로 데드 스토어 제거는 문제이지만 매우 취약하며 컴파일러가 데드 스토어를 지배하는 동시에 데드 스토어를 볼 수있는 등 일반적인 모든주의 사항이 적용됩니다. 내 의견은 Frank가 "case in point : (godbolt link)"라고 말했기 때문에 무엇을 말하려고했는지 더 명확하게 설명했지만 링크는 두 상점 모두 수행되고 있음을 보여줍니다.
BeeOnRope 2018

답변:


33

귀하가 이미 가지고있는 솔루션은 정확하며 코드를 검토하고 있는지 정확히 알고 싶습니다. 최대한 효율적이고 명확하며 간결합니다.


1
내가 가진 주요 문제는 uninit_tag이 관용구를 사용하려는 모든 장소에서 새로운 맛을 선언 해야하는지 여부 입니다. 나는 이미 그러한 지표 유형과 같은 것이 있었기를 바랐습니다 std::.
BeeOnRope 2018

9
표준 라이브러리에서 분명한 선택은 없습니다. 이 기능을 원하는 모든 클래스에 대해 새 태그를 정의하지 않을 것입니다. 프로젝트 전체 no_init태그를 정의하고 필요한 모든 클래스에서 사용합니다.
존 즈 빙크

2
나는 표준 라이브러리는 반복자와 같은 물건과 두 차별화를위한 남자 다운 태그 있다고 생각 std::piecewise_construct_tstd::in_place_t. 그들 중 어느 것도 여기에서 사용하기에 합리적으로 보이지 않습니다. 어쩌면 항상 사용하도록 유형의 전역 객체를 정의하고 싶을 때마다 모든 생성자 호출에 중괄호가 필요하지 않습니다. STL은 std::piecewise_constructfor 와 함께이 작업을 수행 합니다 std::piecewise_construct_t.
n314159

가능한 효율적이지 않습니다. 인스턴스에 대한 AArch64 호출 규칙에서 태그는 스택에 할당되어야한다와 노크에 미치는 영향 (수 없습니다 꼬리 전화를하거나 ...) : godbolt.org/z/6mSsmq
TLW

1
@TLW 생성자에 본문을 추가하면 스택 할당이 없습니다. godbolt.org/z/vkCD65
R2RT

8

생성자 본문이 비어 있으면 생략하거나 기본값으로 지정할 수 있습니다.

struct event_counts {
    std::uint64_t counts[MAX_COUNTERS];
    event_counts() = default;
};

그런 다음 기본 초기화 event_counts counts;counts.counts초기화되지 않은 상태로 유지되며 (기본 초기화는 no-op입니다) 값 초기화 event_counts counts{}; 는 initialize 값을 counts.counts지정하여 효과적으로 0으로 채 웁니다.


3
그러나 다시 값 초기화를 사용해야하며 OP는 기본적으로 안전하기를 원합니다.
doc

@doc, 동의합니다. 이것은 OP가 원하는 것에 대한 정확한 해결책이 아닙니다. 그러나이 초기화는 내장 유형을 모방합니다. 들어 int i;우리가 받아들이는 제로가 초기화되지 않았는지 확인합니다. 아마도 우리 event_counts counts;는 그것이 0으로 초기화되지 않았다는 것을 받아들이고 event_counts counts{};새로운 기본값을 만들어야 합니다.
Evg

6

나는 당신의 해결책을 좋아합니다. 중첩 구조체와 정적 변수를 고려했을 수도 있습니다. 예를 들면 다음과 같습니다.

struct event_counts {
    static constexpr struct uninit_tag {} uninit = uninit_tag();

    uint64_t counts[MAX_COUNTS];

    event_counts() : counts{} {}

    explicit event_counts(uninit_tag) {}

    // more stuff

};

정적 변수를 사용하면 초기화되지 않은 생성자 호출이 더 편리해 보일 수 있습니다.

event_counts e(event_counts::uninit);

물론 타이핑을 저장하고보다 체계적인 기능을 만들기 위해 매크로를 도입 할 수 있습니다

#define UNINIT_TAG static constexpr struct uninit_tag {} uninit = uninit_tag();

struct event_counts {
    UNINIT_TAG
}

struct other_counts {
    UNINIT_TAG
}

3

나는 열거 형이 태그 클래스 나 부울보다 더 나은 선택이라고 생각합니다. 구조체의 인스턴스를 전달할 필요가 없으며 호출자가 어떤 옵션을 받고 있는지 분명합니다.

struct event_counts {
    enum Init { INIT, NO_INIT };
    uint64_t counts[MAX_COUNTERS];

    event_counts(Init init = INIT) {
        if (init == INIT) {
            std::fill(counts, counts + MAX_COUNTERS, 0);
        }
    }
};

그런 다음 인스턴스 생성은 다음과 같습니다.

event_counts e1{};
event_counts e2{event_counts::INIT};
event_counts e3{event_counts::NO_INIT};

또는 태그 클래스 접근 방식과 비슷하게하려면 태그 클래스 대신 단일 값 열거 형을 사용하십시오.

struct event_counts {
    enum NoInit { NO_INIT };
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}
    explicit event_counts(NoInit) {}
};

그런 다음 두 가지 방법으로 인스턴스를 만들 수 있습니다.

event_counts e1{};
event_counts e2{event_counts::NO_INIT};

나는 당신에게 동의합니다 : 열거 형이 더 간단합니다. 그러나 아마도 당신은이 줄을 잊었을 것이다 :event_counts() : counts{} {}
푸르스름한

@bluish, 내 의도는 counts무조건 초기화하는 것이 아니라 INIT설정된 경우에만 초기화하는 것이 었습니다 .
TimK

@bluish 태그 클래스를 선택하는 주된 이유는 단순성을 달성하는 것이 아니라 초기화되지 않은 객체가 특별하다는 것을 신호하는 것입니다. 즉, 클래스 인터페이스의 일반적인 부분보다는 최적화 기능을 사용합니다. 모두 boolenum괜찮은 있지만, 우리는 과부하 대신 매개 변수를 사용하는 것은 다소 다른 의미 론적 그늘을 가지고 있음을 알고 있어야합니다. 전자에서는 객체를 명확하게 패러미터 화하기 때문에 초기화 / 초기화되지 않은 자세가 상태가되는 반면, 태그 객체를 ctor에 전달하는 것은 클래스에게 변환을 수행하도록 요청하는 것과 비슷합니다. 따라서 구문 선택의 문제는 IMO가 아닙니다.
doc

@TimK 그러나 OP는 기본 동작이 배열의 초기화가되기를 원하므로 질문에 대한 솔루션에 포함해야한다고 생각합니다 event_counts() : counts{} {}.
푸르스름한

@bluish 내 원래 제안에서 요청 countsstd::fill없으면 NO_INIT로 초기화됩니다 . 제안한대로 기본 생성자를 추가하면 기본 초기화를 수행하는 두 가지 방법이 있으므로 좋은 생각이 아닙니다. 을 사용하지 않는 다른 접근법을 추가했습니다 std::fill.
TimK

1

클래스 의 2 단계 초기화 를 고려할 수 있습니다 .

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() = default;

    void set_zero() {
       std::fill(std::begin(counts), std::end(counts), 0u);
    }
};

위의 생성자는 배열을 0으로 초기화하지 않습니다. 배열의 요소를 0으로 설정하려면 set_zero()생성 후 멤버 함수를 호출해야합니다 .


7
고마워, 나는이 접근법을 고려했지만 기본 안전을 유지하는 것을 원합니다 (예 : 기본적으로 0). 선택한 소수의 장소에서만 동작을 안전하지 않은 것으로 무시합니다.
BeeOnRope

3
초기화되지 않은 용도를 제외 하고는 별도의주의가 필요합니다 . 따라서 OP 솔루션과 관련된 추가 버그 소스입니다.
호두

@BeeOnRope 하나는 기본 인수 std::function와 비슷한 것을 생성자 인수로 제공 할 수도 있습니다 set_zero. 초기화되지 않은 배열을 원하면 람다 함수를 전달합니다.
doc

1

나는 이렇게 할 것입니다 :

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}

    event_counts(bool initCounts) {
        if (initCounts) {
            std::fill(counts, counts + MAX_COUNTERS, 0);
        }
    }
};

컴파일러는를 사용할 때 모든 코드를 건너 뛸 정도로 똑똑 event_counts(false)하며 클래스의 인터페이스를 이상하게 만드는 대신 의미하는 바를 정확하게 알 수 있습니다.


8
효율성에 대해서는 맞지만 부울 매개 변수는 읽을 수있는 클라이언트 코드를 만들지 않습니다. 당신이 함께 읽고 선언을 볼 때 event_counts(false), 그것은 무엇을 의미합니까? 되돌아 가서 매개 변수 의 이름 을 보지 않으면 아무 생각이 없습니다 . 질문에 표시된 것처럼 최소한 열거 형 또는이 경우 센티넬 / 태그 클래스를 사용하는 것이 좋습니다. 그런 다음보다 유사한 선언을 얻습니다 event_counts(no_init). 이는 모든 사람에게 그 의미가 분명합니다.
코디 그레이

나는 이것이 괜찮은 해결책이라고 생각한다. 기본 ctor를 버리고 기본값을 사용할 수 있습니다 event_counts(bool initCountr = true).
doc

또한 ctor는 명시 적이어야합니다.
doc

불행히도 현재 C ++은 명명 된 매개 변수를 지원하지 않지만 가독성을 사용 boost::parameter하고 호출 할 수 있습니다.event_counts(initCounts = false)
phuclv

1
재미있게도 @doc 모든 매개 변수에 기본값이 있기 때문에 event_counts(bool initCounts = true)실제로 기본 생성자입니다. 요구 사항은 인수를 지정하지 않고 호출 할 수 있어야하며 매개 변수가 없거나 기본값을 사용하는지는 신경 쓰지 않습니다. event_counts ec;
저스틴 타임-복원 모니카

1

약간의 타이핑을 저장하기 위해 서브 클래스를 사용합니다.

struct event_counts {
    uint64_t counts[MAX_COUNTERS];

    event_counts() : counts{} {}
    event_counts(uninit_tag) {}
};    

struct event_counts_no_init: event_counts {
    event_counts_no_init(): event_counts(uninit_tag{}) {}
};

초기화하지 않는 생성자의 인수를 bool또는로 변경하여 더미 클래스를 제거 할 수 있습니다int 그것은 더 이상 연상 할 필요가 없기 때문에, 또는 뭔가.

상속을 events_count_no_init바꾸고 답변에서 제안 된 Evg와 같은 기본 생성자로 정의 한 다음 events_count하위 클래스 가 될 수 있습니다.

struct event_counts_no_init {
    uint64_t counts[MAX_COUNTERS];
    event_counts_no_init() = default;
};

struct event_counts: event_counts_no_init {
    event_counts(): event_counts_no_init{} {}
};

이것은 흥미로운 아이디어이지만 새로운 유형을 도입하면 마찰이 발생할 것 같습니다. 예를 들어, 실제로 초기화되지 않은을 원할 때 event_counts유형 event_count이 아닌 을 원할 것입니다.event_count_uninitialized . 따라서 시공을 슬라이스해야합니다 event_counts c = event_counts_no_init{};. 타이핑의 절감을 거의 없애줍니다.
BeeOnRope

@BeeOnRope 글쎄, 대부분의 목적을 위해 event_count_uninitialized객체는event_count 객체이다. 그것은 상속의 요점이며 완전히 다른 유형은 아닙니다.
로스 릿지

동의하지만 문지름은 "대부분의 목적을 위해"입니다. 그것들은 상호 교환이 불가능합니다-예를 들어, 할당 ecu을 시도 ec하면 다른 방법으로는 작동하지 않습니다. 또는 템플릿 함수를 사용하는 경우 동작이 동일하더라도 (종종 정적 템플릿 멤버와 같지 않은 경우에도) 유형이 다르고 다른 인스턴스화로 끝납니다. 특히 auto이것을 많이 사용 하면 확실히 자르고 혼란 스러울 수 있습니다. 객체가 초기화되는 방식이 유형에 영구적으로 반영되는 것을 원하지 않습니다.
BeeOnRope
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.