2012-03-14 4 views
0

Nifty Counter C++ Idiom으로 정적 멤버를 초기화 할 때 몇 가지 문제가 있습니다. 다음의 경우에 ideom을 올바르게 사용하는 방법을 설명 할 수 있습니까?Nifty Counter C++ Idiom을 사용할 때 생성자를 두 번 호출해야합니까?

다음과 같은 문제가있는 것 같습니다. 다른 정적 단위를 다른 컴파일 단위에 사용합니다. 다른 정적 단위는 다른 정적 단위 (CDataFile)를 사용합니다. 이 경우 초기화 순서는 정의되지 않았으므로 프로덕션 환경에서는 잘못된 순서로 진행 되었기 때문에 Nifty Counter 관용구를 사용하려고했습니다. 하지만 이제는 CDataFile의 정적 멤버가 두 번 초기화됩니다 (생성자가 두 번 호출 됨). 처음에는 생성자가 CDataFileInitializer에서 호출됩니다. 그런 다음 고정 멤버가 사용되며 (mSome이 채워짐) CSomeClass의 생성자가 두 번째로 호출되고 mSome의 내용이 지워집니다. 당신이 네임 스페이스 범위에서 객체를 정의하는 경우

// datafile.h 
class CDataFile 
{ 
    friend class CDataFileInitializer; 
protected: 
    static CSomeClass mSome; 
    // other code 
}; 

static class CDataFileInitializer 
{ 
public: 
    CDataFileInitializer(); 
} dfinitializer; 

// datafile.cpp 
static int nifty_counter; 
CSomeClass CDataFile::mSome; // second initialization comes from here? 

CDataFileInitializer::CDataFileInitializer() 
{ 
    if (!nifty_counter++) 
    { 
     printf("CDataFileInitializer Constructor\n"); 
     CDataFile::mSome= CSomeClass(); // first initialization 
    } 
} 
+0

이것은 멋진 카운터가 아닙니다. 이것은 정의되지 않은 동작입니다. – Mankarse

+0

@Mankarse : 정의되지 않은 이유를 설명해 주시겠습니까? –

+0

'CSomeClass CDataFile :: mSome'의 동적 초기화는'dfinitializer'의 동적 초기화에 대해 불확실한 순서로 배열됩니다. 따라서'CDataFile :: mSome'에서'operator ='가 호출되면 아직 구성되지 않았을 수 있습니다. – Mankarse

답변

3

라인 :

CSomeClass CDataFile::mSome; 

CSomeClass 타입의 변수를 정의한다. 이 변수의 초기화는 두 단계로 진행됩니다. 먼저 0으로 초기화됩니다. (대략) 이것은 해당 메모리가 모두 0으로 설정됨을 의미합니다. 그런 다음 동적 초기화가 발생합니다. 이로 인해 생성자가 실행됩니다.

dfinitializer은 유사한 "제로 - 초기화 후 동적 초기화"패턴을 따른다.동적 초기화 단계에서 CDataFile::mSome에 을 호출하여 CSomeClass()으로 구성된 새 기본값을 mSome에 할당합니다.

mSomedfinitializer의 동적 초기화가 서로에 대해 불확실하게 순서대로 지정되기 때문에이 단계는 전혀 무의미합니다. dfinialiser가 최초로 초기화되면 (자), 아직 작성되어 있지 않은 객체에 할당을 시도해, 나중에 디폴트로 구축됩니다. 초기화 된 객체가 있으면, 이미 작성된 객체에 재 할당됩니다.

대신에 :

CDataFileInitializer::CDataFileInitializer() 
{ 
    if (!nifty_counter++) 
    { 
     printf("CDataFileInitializer Constructor\n"); 
     new (&CDataFile::mSome) CSomeClass(); 
    } 
} 

대안 :

alignas(CSomeClass) unsigned char CDataFile::mSome[sizeof(CSomeClass)]; 

그런 다음에 CDataFileInitializer을 변경

CSomeClass CDataFile::mSome; 

당신은 객체를 구성 할 수있는 스토리지 영역을 만들어야합니다 함수 정적 변수를 사용하는 것입니다 :

CSomeClass& getMSome() { 
    static CSomeClass mSome; 
    return mSome; 
} 

이렇게하면 mSome이 스레드로부터 안전하게 초기화됩니다.

+0

그건 최고의 솔루션, _if_ 귀하의 컴파일러는'alignas' 또는 이와 유사한 (그리고 그것은 변수의 유형에 대한 링크 타임 타입 검사를하지 않으면,하지만 어떤 컴파일러 모르겠다). –

+0

@JamesKanze : 내 대답은 분명하지 않지만 실제로'datafile.h '에서'mSome'의 유형을 변경 (필요할 때마다 캐스팅)하는 것이 좋습니다. 나는 그것이 어떤 타입 제약을 위반하는지를 보지 못합니다. – Mankarse

+0

객체를 사용할 때마다'reinterpret_cast'가 필요하다면 클라이언트는별로 행복하지 않을 것입니다. 공식적으로 한 유형으로 객체를 정의하고이를 다른 객체로 액세스하는 것은 정의되지 않은 동작입니다. 실질적으로 그것을 헤더의'CSomeClass'로 선언하지만, 소스 파일에서했던 것처럼 문제없이 작동 할 것입니다. 그것은 내가 할 것입니다 (만약 내가'alignas'을 가질 수 있는지). –

1

는 생성자가 초기화하는 동안 어떤 시점에서 스타트 업 코드에 의해 를 호출합니다. 멋진 카운터 관용구를 사용하고 싶다면 아무래도 이것을 억제해야합니다 ( ). 실제 이니셜 라이저 내에서 새 게재 위치를 사용해야합니다. 이를 달성하는 방법은 여러 가지가 있습니다

  • 나도 어셈블러에서 개체를 선언하거나 생성자가 호출되지 않습니다 을 보장하기 위해 컴파일러 확장을 사용합니다 본 적이 산업용 강도 구현의 대부분 . 이것은 매우 이식성이 없지만 어쨌든 순수 C++로 구현 될 수없는 iostream과 같은 것에 대해서는 입니다. 종종 허용됩니다. (이것은 사실, 그들이 허용되지 않습니다 때문에 iostream 개체에 대한 유일한 허용 솔루션 은 파괴 될 것이다.) 나는 일반적으로 특별한 무 조작 생성자를 배치 한

  • , 가 수행하는 아무것도. 공식적으로는 작동이 보장되지 않지만 실제로는 입니다. 그런 다음이 생성자를 사용하기 위해 멋진 카운터 관용구를 사용하는 인스턴스를 정의합니다. 그것은 사소한 생성자를 가질 수 있다면 단지 이 초기화는 클래스를 제어 할 경우

  • 마지막으로 건설되고, 유일한 인스턴스가 멋진 카운터에 의해 controled있다, 당신은 생성자에 대한 작업을 할 필요가 없습니다 초기화 프로그램의 다양한 멤버. 이들의

없음 특히 좋은 솔루션없고, 새로운 코드에서, 나는 싱글 관용구의 일부 변종을 사용하십시오.

+0

두 번째 점에서, 아무것도하지 않는 특수한 생성자가 멋진 카운터 코드 부분에서 호출되어야한다고 썼다. 그 반대도 안된다. 기본 생성자는 아무것도 아니고 특별한 생성자는 초기화해야합니까? 세 번째 점 : 초기화 프로그램에서 초기화 메소드 목록 또는 초기화 메소드'Init()'를 의미합니까? 하지만 문제는 남아 있지 않습니다. 정적 객체가 간결한 카운터 코드 부분에서 생성되지 않았거나 - 이미 초기화 된 상태이고 나중에 호출되는 기본 생성자가 아무 것도하지 않아야합니다 (두 번째 요점처럼)? –

+0

@ChristianAmmer 두 번째 점은 다르지만, 개체가 다른 곳에서 사용될 경우 기본 생성자가 정상적으로 작동하기를 원합니다. no-op 생성자는 특수한 경우에만 사용되며 특수한 상황에서만 사용되기 때문에 표시해야합니다. 멋진 카운터 알고리즘에 의해 생성 된 객체의 정의는 'MyClass obj (noopConstructor);'와 같아야 만하므로 독자는 특별한 것이 진행되고 있음을 즉시 알 수 있습니다. 세 번째 경우에는 : 나는 사소한 생성자가 없도록 제안했습니다. –

-1

예 아니요 : 예 두 번 호출되고 두 개의 다른 개체에 대해 호출되지 않습니다.

의 요 때문에 A.cpp 및 B.cpp 모두 dfinitializer의 로컬 및 독립적 인 복사본이됩니다 #INCLUDE의

// A.cpp 
#include "datafile.h" 
... 

// B.cpp 
#include "datafile.h" 
... 

있다고 가정 해 봅시다.

datafile.cpp가 차례로 nifty_counter (초기 0 값 ... static int nifty_counter = 0;)으로 정의하고 CDatafile :: mSome (파일 수준에서 초기화 됨)이 있습니다.

ctor는 mSome에 이미 초기화되고 즉시 만들어지고 소멸되는 임시 CSomeClass()에 할당됩니다.

사실 CDataFile이 할당 가능하기 때문에 실제로 올바른 일을하는 잘못된 구현입니다.

정적 데이터 멤버를 초기화하는 데 문제가 있다면 정적 멤버 정의 (참고 : 정의가 아니라 선언)가 포함 된 모듈의 일부가 다른 부작용을 일으키는 다른 멤버에 의해 호출되는지 확인하면됩니다.

CDataFileInitializer[0x406035] creation 
CDataFileInitializer FIRST INITIALIZATION 
CSome[0x40602c] default created 
initializing A.cpp 
CDataFileInitializer[0x406029] creation 
initializing B.cpp 
CDataFileInitializer[0x406025] creation 
main 
do something in a.ccp 
do something in b.ccp 
main return 
CDataFileInitializer[0x406025] destruction 
cleaning B.cpp 
CDataFileInitializer[0x406029] destruction 
cleaning A.cpp 
CSome[0x40602c] destroyed 
CDataFileInitializer[0x406035] destruction 
CDataFileInitializer LAST DESTRUCTION 
,369 : 모듈이의 다음과 같은 출력을 줄 것이다 더 나은 트릭

//some.h 
#ifndef SOME_H_INCLUDED 
#define SOME_H_INCLUDED 

#include<iostream> 
class CSome 
{ 
public: 
    CSome() { std::cout << "CSome["<<this<<"] default created" << std::endl; } 
    CSome(const CSome& s) { std::cout << "CSome["<<this<<"] created from ["<<&s<<"]" << std::endl; } 
    CSome& operator=(const CSome& s) { std::cout << "CSome["<<this<<"] assigned from ["<<&s<<"]" << std::endl; return *this; } 
    CSome(CSome&& s) { std::cout << "CSome["<<this<<"] created moving ["<<&s<<"]" << std::endl; } 
    CSome& operator=(CSome&& s) { std::cout << "CSome["<<this<<"] assigned moving ["<<&s<<"]" << std::endl; return *this; } 
    ~CSome() { std::cout << "CSome["<<this<<"] destroyed" << std::endl; } 
}; 
#endif // SOME_H_INCLUDED 




//datafile.h 
#ifndef DATAFILE_H_INCLUDED 
#define DATAFILE_H_INCLUDED 

#include "some.h" 
class CDataFile 
{ 
public: 
protected: 
    static CSome mSome; 
}; 

static class CDataFileInitializer 
{ 
public: 
    CDataFileInitializer(); 
    ~CDataFileInitializer(); 
} datafileinitializer; 


#endif // DATAFILE_H_INCLUDED 



//datafile.cpp 
#include "datafile.h" 
#include <iostream> 
static int nifty_counter = 0; //the one and only 

CSome CDataFile::mSome; //define and initialize 

CDataFileInitializer::CDataFileInitializer() 
{ 
    std::cout << "CDataFileInitializer["<<this<<"] creation"<< std::endl; 
    if(!nifty_counter++) 
    { 
     std::cout << "CDataFileInitializer FIRST INITIALIZATION"<< std::endl; 
    } 
} 

CDataFileInitializer::~CDataFileInitializer() 
{ 
    std::cout << "CDataFileInitializer["<<this<<"] destruction"<< std::endl; 
    if(!--nifty_counter) 
    { 
     std::cout << "CDataFileInitializer LAST DESTRUCTION"<< std::endl; 
    } 
} 


//A.cpp 
#include <iostream> 
static class A 
{ 
public: 
    A() { std::cout << "initializing A.cpp" << std::endl; } 
    ~A() { std::cout << "cleaning A.cpp" << std::endl; } 
} a; 
#include "datafile.h" 
// other a.cpp code ... 

void call_a() { std::cout << "do something in a.ccp" << std::endl; } 


//B.cpp 
#include <iostream> 
static class B 
{ 
public: 
    B() { std::cout << "initializing B.cpp" << std::endl; } 
    ~B() { std::cout << "cleaning B.cpp" << std::endl; } 
} b; 
#include "datafile.h" 
// other b.cpp code ... 

void call_b() { std::cout << "do something in b.ccp" << std::endl; } 


//main.cpp 
#include <iostream> 

void call_a(); 
void call_b(); 

int main() 
{ 
    std::cout << "main" << std::endl; 
    call_a(); 
    call_b(); 
    std::cout << "main return" << std::endl; 
    return 0; 
} 

를 해보자 ...

그래서 (그냥 최적화 할 피하기 위해)

코스 중 주소는 컴퓨터에 따라 변경되어 실행됩니다.

관련 문제