원샷 'if'를 작성하는 가장 우아한 방법


136

C ++ 17부터 if다음과 같이 정확히 한 번만 실행될 블록을 작성할 수 있습니다 .

#include <iostream>
int main() {
    for (unsigned i = 0; i < 10; ++i) {

        if (static bool do_once = true; do_once) { // Enter only once
            std::cout << "hello one-shot" << std::endl;
            // Possibly much more code
            do_once = false;
        }

    }
}

나는 이것을 지나치게 생각할 수도 있고 이것을 해결할 다른 방법이 있지만 여전히-어떻게 든 이것을 쓸 수 do_once = false있습니까? 그래서 결국에는 필요가 없습니까?

if (DO_ONCE) {
    // Do stuff
}

do_once()포함 하는 도우미 함수를 생각하고 static bool do_once있지만 다른 위치에서 동일한 함수를 사용하려면 어떻게해야합니까? 이 시간과 장소가 될 수 #define있을까요? 내가하지 희망.


52
왜 안돼 if (i == 0)? 충분합니다.
SilvanoCerza

26
@SilvanoCerza 그게 요점이 아니기 때문에. 이 if-block은 일반적인 루프가 아닌 여러 번 실행되는 함수의
nada

8
아마도 std::call_once옵션 일 수도 있습니다 (스레딩에 사용되지만 여전히 작동합니다).
fdan

25
당신의 예는 당신이 우리에게 보여주지 않는 실제 문제를 잘못 반영했을 수도 있지만, 왜 call-once 함수를 루프에서 해제하지 않습니까?
rubenvb 2016 년

14
if조건 에서 초기화 된 변수 가 될 수 있다는 것은 나에게 발생하지 않았습니다 static. 영리 해요
HolyBlackCat

답변:


143

사용 std::exchange:

if (static bool do_once = true; std::exchange(do_once, false))

당신은 진실의 가치를 뒤집는 것을 더 짧게 만들 수 있습니다 :

if (static bool do_once; !std::exchange(do_once, true))

그러나 이것을 많이 사용한다면, 화려하지 말고 대신 래퍼를 만드십시오.

struct Once {
    bool b = true;
    explicit operator bool() { return std::exchange(b, false); }
};

그리고 그것을 다음과 같이 사용하십시오 :

if (static Once once; once)

변수는 조건 외부에서 참조되지 않아야하므로 이름은 우리를 많이 사지 않습니다. 식별자와 관련 하여 파이썬 과 같은 다른 언어에서 영감을 받아 다음과 같이 _작성할 수 있습니다.

if (static Once _; _)

추가 개선 사항 : BSS 섹션 (@Deduplicator)을 활용하고 이미 실행했을 때 메모리 쓰기를 피하고 (@ShadowRanger) 여러 번 테스트 할 경우 (예 : 질문과 같이) 분기 예측 힌트를 제공하십시오.

// GCC, Clang, icc only; use [[likely]] in C++20 instead
#define likely(x) __builtin_expect(!!(x), 1)

struct Once {
    bool b = false;
    explicit operator bool()
    {
        if (likely(b))
            return false;

        b = true;
        return true;
    }
};

32
나는 매크로가 C ++에서 많은 증오를 알고 있다는 것을 알고 있지만 이것은 매우 깨끗해 보입니다. 다음 #define ONLY_ONCE if (static bool DO_ONCE_ = true; std::exchange(DO_ONCE_, false))과 같이 사용됩니다.ONLY_ONCE { foo(); }
Fibbles

5
내 말은, 만약 당신이 "한 번"을 세 번 쓴다면, 가치있는 if 서술문에서 그것을 세 번 이상 사용하십시오
Alan

13
이 이름 _은 번역 가능한 문자열을 표시하기 위해 많은 소프트웨어에서 사용됩니다. 흥미로운 일이 일어날 것으로 예상하십시오.
Simon Richter

1
선택 사항이있는 경우 정적 상태의 초기 값을 모두 비트 0으로 설정하십시오. 대부분의 실행 가능 형식에는 영 (0) 영역의 길이가 포함됩니다.
중복 제거기

7
_변수에 사용하면 비피 토닉이 있습니다. 당신은 사용하지 않는 _경우에만 값을 저장, 나중에 참조 될 변수 변수를 제공하지만 값이 필요하지 않습니다. 일반적으로 일부 값만 필요할 때 포장 풀기에 사용됩니다. (다른 사용 사례가 있지만
버리기

91

아마도 가장 우아한 해결책은 아니며 실제를 볼 수 if는 없지만 표준 라이브러리는 실제로이 경우를 다룹니다 std::call_once.

#include <mutex>

std::once_flag flag;

for (int i = 0; i < 10; ++i)
    std::call_once(flag, [](){ std::puts("once\n"); });

여기서 장점은 스레드 안전하다는 것입니다.


3
그 맥락에서 std :: call_once를 알지 못했습니다. 그러나이 솔루션을 사용하면 std :: call_once를 사용하는 모든 장소에 대해 std :: once_flag를 선언해야합니까?
nada

11
이것은 효과가 있지만 단순한 if 솔루션을위한 것이 아니라 멀티 스레드 응용 프로그램을위한 아이디어입니다. 내부 동기화를 사용하기 때문에 단순한 경우에는 과잉입니다. 모두 if로 해결할 수있는 것입니다. 그는 스레드 안전 솔루션을 요구하지 않았습니다.
Michael Chourdakis 2014 년

9
@MichaelChourdakis 당신에게 동의합니다, 그것은 과잉입니다. 그러나, 알아볼 가치가없는 if-trickery 뒤에 무언가를 숨기지 않고 무엇을하고 있는지 ( "이 코드를 한 번만 실행") 표현할 수있는 가능성에 대해 알고 있어야합니다.
lubgr 2016 년

17
어, 저를 보는 것은 call_once당신이 한 번만 전화하고 싶다는 것을 의미합니다. 미쳤어
Barry

3
@SergeyA는 내부 동기화를 사용하기 때문에 평범한 경우 해결할 수있는 모든 것. 요청 된 것 이외의 것을 수행하는 관용적 인 방법입니다.
궤도에서 가벼움 레이스

52

C ++에는 이미 "( before-block; condition; after-block )"으로 구성된 제어 흐름 기본 요소가 내장되어 있습니다 .

for (static bool b = true; b; b = false)

또는 해커이지만 짧습니다.

for (static bool b; !b; b = !b)

그러나, 여기에 제시된 기술은 매우 일반적이지 않기 때문에주의해서 사용해야한다고 생각합니다.


1
나는 첫 번째 옵션을 좋아합니다 (여기의 많은 변형과 마찬가지로 스레드 안전하지 않으므로 조심하십시오). 두 번째 옵션은 ... 나를 읽고 (열심히 오한과 두 개의 스레드를 여러 번 실행될 수 있습니다 b == false: Thread 1평가합니다 !b및 루프에 대한 입력 Thread 2들을 평가 !b및 루프 입력, Thread 1설정, 루프를 위해 자사의 물건과 잎을 수행 b == false!bb = true... Thread 2루프, 설정의 물건과 잎 수행 b == true!bb = false수, 전체 프로세스 것은 무한정 반복 할)
CharonX

3
일부 코드를 한 번만 실행 해야하는 문제에 대한 가장 우아한 솔루션 중 하나는 루프 라는 것이 아이러니하다는 것을 알았습니다 . +1
nada 2016 년

2
나는 피할 것이다 b=!b, 그것은 멋지게 보이지만 실제로는 값이 거짓이기를 원하기 때문에 b=false선호된다.
yo '

2
보호되지 않은 블록이 로컬에서 종료되면 다시 실행됩니다. 그것은 바람직 할 수도 있지만, 다른 모든 접근법과는 다릅니다.
Davis Herring

29

C ++ 17에서는 다음을 작성할 수 있습니다.

if (static int i; i == 0 && (i = 1)){

i루프 본체 에서 장난을 피하기 위해 . i(표준 보증) 0으로 시작하고, 이후 발현 ;세트 i1그 평가 처음.

C ++ 11에서는 람다 함수를 사용하여 동일한 결과를 얻을 수 있습니다.

if ([]{static int i; return i == 0 && (i = 1);}()){

i루프 몸체에 누출되지 않는 장점도 있습니다 .


4
CALL_ONCE라는 #define에 넣으면 더 읽기 쉽습니다.
nada

9
하지만 static int i;힘이 경우 중 하나 (나는 확실하지 진정 해요) i으로 초기화 보장 0, 그것을 사용하는 것이 훨씬 명확입니다 static int i = 0;여기.
Kyle Willmon

7
어쨌든 나는 이니셜 라이저가 이해하기에 좋은 아이디어라는 것에 동의한다
Orbit in Lightbit 레이스

5
@Bathsheba Kyle은 그렇게하지 않았으므로 귀하의 주장은 이미 거짓으로 판명되었습니다. 명확한 코드를 위해 두 개의 문자를 추가하는 데 드는 비용은 무엇입니까? 자, 당신은 "최고 소프트웨어 설계자"입니다. 당신은 이것을 알아야합니다 :)
궤도에서 가벼운 레이스

5
변수의 초기 값을 철자하는 것이 명확하지 않다고 생각하거나 "무슨 일이 일어나고있다"고 제안한다면 도움이 될 수 없다고 생각합니다.)
Orbit in Race in

14
static bool once = [] {
  std::cout << "Hello one-shot\n";
  return false;
}();

이 솔루션은 다른 많은 제안과 달리 스레드로부터 안전합니다.


3
()람다 선언에서 선택 사항 (비어있는 경우) 임을 알고 있습니까?
Nonyme

9

조건부 대신 인스턴스화하는 정적 객체의 생성자에서 일회성 작업을 래핑 할 수 있습니다.

예:

#include <iostream>
#include <functional>

struct do_once {
    do_once(std::function<void(void)> fun) {
        fun();
    }
};

int main()
{
    for (int i = 0; i < 3; ++i) {
        static do_once action([](){ std::cout << "once\n"; });
        std::cout << "Hello World\n";
    }
}

또는 실제로 매크로를 사용하면 다음과 같이 보일 수 있습니다.

#include <iostream>

#define DO_ONCE(exp) \
do { \
  static bool used_before = false; \
  if (used_before) break; \
  used_before = true; \
  { exp; } \
} while(0)  

int main()
{
    for (int i = 0; i < 3; ++i) {
        DO_ONCE(std::cout << "once\n");
        std::cout << "Hello World\n";
    }
}

8

@damon이 말했듯 std::exchange이 감소하는 정수를 사용 하여 사용 을 피할 수는 있지만 음수 값은 true로 해석된다는 것을 기억해야합니다. 이것을 사용하는 방법은 다음과 같습니다.

if (static int n_times = 3; n_times && n_times--)
{
    std::cout << "Hello world x3" << std::endl;
} 

이것을 @Acorn의 멋진 래퍼로 변환하면 다음과 같습니다.

struct n_times {
    int n;
    n_times(int number) {
        n = number;
    };
    explicit operator bool() {
        return n && n--;
    };
};

...

if(static n_times _(2); _)
{
    std::cout << "Hello world twice" << std::endl;
}

7

std::exchange@Acorn이 제안한대로 사용 하는 것이 가장 관용적 인 방법 일 수 있지만 교환 작업이 반드시 저렴한 것은 아닙니다. 물론 정적 초기화는 스레드 안전성을 보장하지만 (컴파일러에게 지시하지 않는 한) static키워드 에 대해 성능에 대한 고려는 다소 쓸모가 없습니다 .

당신은 (종종 C ++를 사용하는 사람들로) 마이크로 최적화에 대해 우려하는 경우, 당신은뿐만 아니라 흠집을 낼 수있는 bool사용이 int오히려 후행 감소 (또는, 사용할 수있는 대신 증가를 달리로, bool을 감소시키는 int것입니다 하지 제로로 포화 ...) :

if(static int do_once = 0; !do_once++)

예전 bool에는 증분 / 감소 연산자가 있었지만 오래 전에 더 이상 사용되지 않았으며 (C ++ 11? 확실하지 않습니까?) C ++ 17에서 완전히 제거되었습니다. 그럼에도 불구하고 int그냥 벌금 을 줄일 수 있으며 물론 부울 조건으로 작동합니다.

보너스 : 당신은 do_twice또는 do_thrice유사하게 구현할 수 있습니다 ...


나는 이것을 테스트했고 처음을 제외하고 여러 번 발사했다.
nada

@nada : 바보 같은, 맞아 ... 수정. bool예전에는 한 번 작업하고 줄 였습니다. 그러나 증가는에서 잘 작동합니다 int. 온라인 데모보기 : coliru.stacked-crooked.com/a/ee83f677a9c0c85a
Damon

1
이것은 여전히 ​​여러 번 실행되어 do_once랩을하고 결국 0을 반복해서 (그리고 다시 반복해서칩니다) 문제가 있습니다.
nada

더 정확하게 말하면 : 이제 INT_MAX 시간마다 실행됩니다.
nada

글쎄,하지만 루프 카운터 를 제외 하고는이 경우에는 문제가되지 않습니다. 20 억 (또는 서명되지 않은 경우 40 억)의 반복을 실행하는 사람은 거의 없습니다. 그렇게해도 여전히 64 비트 정수를 사용할 수 있습니다. 가장 빠른 컴퓨터를 사용하면 컴퓨터가 감겨지기 전에 죽을 수 있으므로 고소 당할 수 없습니다.
데이먼

4

@Bathsheba의 훌륭한 답변을 바탕으로 훨씬 간단 해졌습니다.

에서 C++ 17간단하게 수행 할 수 있습니다.

if (static int i; !i++) {
  cout << "Execute once";
}

(이전 버전에서는 int i블록 외부에서 선언 하십시오. C :에서도 작동합니다).

간단히 말하면, i를 선언하면 기본값은 0 ( 0)입니다. 0은 거짓이므로 느낌표 ( !) 연산자를 사용하여 무효화합니다. 그런 다음 <ID>++연산자 의 증가 속성을 고려하여 먼저 처리 (할당 등) 한 다음 증가시킵니다.

따라서이 블록에서 i가 초기화되고 0블록이 실행될 때 한 번만 값을 가지면 값이 증가합니다. 우리는 단순히 !연산자를 사용하여 부정합니다.


1
이것이 이전에 게시 된 경우 아마도 아마도 받아 들여진 대답 일 것입니다. 정말 고마워요!
nada
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.