2

"정적 통계"기능이 아직 구현되지 않은 Visual Studio 2013을 사용 중이므로 로컬 정적 변수 초기화가 사용되지 않습니다. 아직 스레드로부터 안전합니다.로컬 정적 std :: once_flag 및 로컬 정적 포인터를 사용하여 정적 변수의 스레드 안전 초기화

std::unique_ptr<Foo> gp_foo; 
std::once_flag g_flag; 

Foo& GetInstance() 
{ 
    std::call_once(g_flag, [](){ gp_foo = std::make_unique<Foo>(); });  
    return *gp_foo; 
} 

하지만 정적 초기화의 순서로, 첫 번째 (문제를 gp_foog_flag 전역 변수를 가지고있는 아이디어를 좋아하지 않는다 : 그래서, 대신

Foo& GetInstance() 
{ 
    static Foo foo; 
    return foo; 
} 

의 나는 같은 것을 할 두 번째, 필요한 경우에만, 즉 GetInstance()에 대한 첫 번째 호출 이후에 변수를 초기화하고 싶습니다. 따라서 다음을 구현했습니다.

Foo& GetInstance() 
{ 
    // I replaced a smart pointer 
    // with a raw one just to be more "safe" 
    // not sure such replacing is really needed 
    static Foo *p_foo = nullptr; 

    static std::once_flag flag; 
    std::call_once(flag, [](){ p_foo = new Foo; });  
    return *p_foo; 
} 

그리고 (적어도 테스트를 통과 한) 작동하지만, 스레드 안전성이 확실하지 않습니다. 왜냐하면 여기서 정적 로컬 변수 p_fooflag을 여러 스레드로 초기화 할 때 동일한 잠재적 인 문제가 있기 때문입니다. nullptr 및 초기화가 std::once_flag 인 원시 포인터를 초기화하는 것은 Foo의 생성자를 호출하는 것보다 더 결백 한 것처럼 보이지만 실제로 안전한지 여부를 알고 싶습니다.

마지막 코드 단편에 문제가 있습니까?

+0

https를 반환하는 제안 : //codereview.stackexchange합니다. com/ – Blacktempel

+0

[call_once on cppreference] (http://en.cppreference.com/w/cpp/thread/call_once)를 참조하십시오. 특히 글 머리 기호 2. – Simple

+0

@Simple _ 그룹의 호출은 선택한 함수가 성공적으로 완료되기 전에 반환됩니다. 즉, 예외를 통해 종료되지 않습니다. _하지만 내 관심사는 std ::에 대한 것이 아닙니다. call_once가 아니라이 두 줄에 대해서 :'static Foo * p_foo = nullptr; std :: once_flag flag; ' – undermind

답변

0

Foo& GetInstance()이 동일한 컴파일 단위의 일부일 경우 초기화 순서가 정의되므로 스레드로부터 안전합니다.

그러나 위와 같은 경우가 아니고 여러 컴파일 단위가 참조하는 경우 초기화 순서는 Foo& GetInstance()에 대한 호출 순서에 따라 다르며 여러 스레드가 관련된 경우 순서는 정의되지 않으므로 스레드로부터 안전하지 않습니다. 확인 가치

:

+0

그러나 마지막 스 니펫에는 로컬 정적 변수 만 있습니다. – undermind

+0

네,하지만 같은 시간에'Foo & GetInstance()'를 호출하는 두 개의 쓰레드에 대해 true/reproducible 일 수 있습니다. – Griffin

+0

@undermind는 'C++ 11'에서이 코드가 [보이는 것] (https://stackoverflow.com/questions/8102125/is-local-static-variable-initialization-thread-safe-in-c11)이라고 말하고 있습니다. 어쩌면 스레드로부터 안전합니다. 지금 나는 잘 모르겠다. 아마 다른 누군가가 정교하게 만들 수있다. – Griffin

1

은 지금까지 단일 개체 초기화에 가장 안정적인 방법은 schwartz_counter입니다. 전역 객체의 초기화 순서에 관계없이 std::cin, cout 등이 구현되는 방식 및 항상 작동하는 방식입니다.

모든 버전의 C++에서 작동합니다.

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter

+0

BTW,로드시 모든 전역 변수 초기화가 동일한 스레드에서 수행된다는 보장이 있습니까? [Delay-Loaded DLLs] 2 개 (예 : https://msdn.microsoft.com/en-us/library/151kt790.aspx), _lib1.dll_ 함수 _func1_ 및 _lib2.dll_ 함수 _func2_, _func1_ 및 _func2_은 다른 스레드에서 동시에 호출됩니까? – undermind

+0

어쨌든 우리는'int'를'std :: atomic '으로 안전하게 대체 할 수 있기 때문에 실제로이 관용구를 사용하지는 않습니다. 그래서 첫 번째 질문은 호기심에서 벗어납니다. – undermind

+0

@undermind 슈왈츠 계수 객체는 초기화되지 않은 전역 변수가 초기화 된 후에 main이 실행되기 전에 초기화됩니다. DLL에서는 DllMain이 실행되기 직전에 발생합니다. 모든 스레드 첨부 코드 이전에 발생한다고 생각됩니다. 하지만 오랫동안 DLL을 사용하지 않았으므로 확인해야합니다. 그들을 참을 수 없어! –

0

귀하의 마지막 코드는보기의 스레드 안전 초기화 지점에서 괜찮습니다.

그러나 GetInstance을 호출하는 스레드에서 Foo 개체를 어떻게 사용할 것인지 명확하지 않습니다. const가 아닌 객체에 대한 참조를 반환하기 때문에 스레드가 Foo 객체를 수정할 수 있다고 생각합니다. 이 작업을 수행하려면 추가 동기화가 필요합니다.Foo 객체가 완전히 생성자 만 개체에서 읽 GetInstance를 호출 스레드에 의해 초기화되는 mutex)

경우,이 문제가되지 않습니다하지만 난 const Foo &