C ++ 정적 멤버 초기화 (내부 템플릿 재미)
-
10-07-2019 - |
문제
정적 멤버 초기화의 경우 중첩 헬퍼 구조물을 사용하여 비 템플릿 클래스에 적합합니다. 그러나, 동봉 클래스가 템플릿에 의해 매개 변수화되면, 기본 코드에서 도우미 객체에 액세스하지 않으면 중첩 초기화 클래스가 인스턴스화되지 않습니다. 예를 들어 단순화 된 예제 (제 경우에는 벡터를 초기화해야 함).
#include <string>
#include <iostream>
struct A
{
struct InitHelper
{
InitHelper()
{
A::mA = "Hello, I'm A.";
}
};
static std::string mA;
static InitHelper mInit;
static const std::string& getA(){ return mA; }
};
std::string A::mA;
A::InitHelper A::mInit;
template<class T>
struct B
{
struct InitHelper
{
InitHelper()
{
B<T>::mB = "Hello, I'm B."; // [3]
}
};
static std::string mB;
static InitHelper mInit;
static const std::string& getB() { return mB; }
static InitHelper& getHelper(){ return mInit; }
};
template<class T>
std::string B<T>::mB; //[4]
template<class T>
typename B<T>::InitHelper B<T>::mInit;
int main(int argc, char* argv[])
{
std::cout << "A = " << A::getA() << std::endl;
// std::cout << "B = " << B<int>::getB() << std::endl; // [1]
// B<int>::getHelper(); // [2]
}
G ++ 4.4.1 :
1] 및 [2]는 다음과 같이 언급했다.
A = Hello, I'm A.
의도 한대로 작동합니다
1] 타당한 :
A = Hello, I'm A. B =
inithelper가 MB를 초기화 할 것으로 기대합니다
- 1] 및 [2] 타당한 :
A = Hello, I'm A. B = Hello, I'm B.
의도 한대로 작동합니다 - 1] 댓글, [2] 무의미한 :
3]의 정적 초기화 단계에서의 Segfault
따라서 내 질문 : 이것은 컴파일러 버그입니까, 아니면 버그가 모니터와 의자 사이에 앉아 있습니까? 그리고 후자가 그 사실이라면 : 우아한 해결책이 있습니까 (즉, 정적 초기화 방법을 명시 적으로 호출하지 않고)?
업데이트 I :
이것은 원하는 동작으로 보인다 (ISO/IEC C ++ 2003 표준, 14.7.1에 정의 된 바와 같이) :
클래스 템플릿 또는 멤버 템플릿의 구성원이 명시 적으로 인스턴스화되거나 명시 적으로 전문화되지 않는 한, 멤버 정의가 존재 해야하는 컨텍스트에서 전문화가 참조 될 때 멤버의 전문화가 암시 적으로 인스턴스화됩니다. 특히, 정적 데이터 멤버 자체가 정적 데이터 구성원의 정의가 존재하도록 요구하는 방식으로 자체를 사용하지 않는 한 정적 데이터 구성원의 초기화 (및 관련 부작용)는 발생하지 않습니다.
해결책
이것은 얼마 전에 Usenet에서 논의되었으며, 나는 stackoverflow에서 또 다른 질문에 대답하려고했습니다. 정적 데이터 구성원의 인스턴스화 지점. 테스트 사례를 줄이고 각 시나리오를 고립하여 고려할 가치가 있다고 생각합니다.
struct C { C(int n) { printf("%d\n", n); } };
template<int N>
struct A {
static C c;
};
template<int N>
C A<N>::c(N);
A<1> a; // implicit instantiation of A<1> and 2
A<2> b;
정적 데이터 멤버 템플릿의 정의가 있습니다. 이것은 아직 데이터 구성원을 만들지 않습니다. 14.7.1
:
"... 특히 정적 데이터 멤버 자체가 정적 데이터 멤버의 정의가 존재하도록 요구하는 방식으로 사용되지 않는 한 정적 데이터 구성원의 초기화 (및 관련 부작용)는 발생하지 않습니다."
해당 단어를 정의하는 한 정의 규칙에 따라 해당 엔티티가 "사용"할 때 무언가 (= 엔티티)의 정의가 필요합니다. 3.2/2
). 특히, 모든 참조가 끊임없는 템플릿에서 나온 경우 템플릿 또는 sizeof
표현식 또는 유사한 것들은 엔티티를 "사용"하지 않는 것입니다 (잠재적으로 평가하지 않거나 아직 사용되는 함수/멤버 함수로 아직 존재하지 않기 때문에 그러한 정적 데이터 구성원은 인스턴스화되지 않습니다. .
암시 적 인스턴스화 14.7.1/7
정적 데이터 구성원의 선언을 인스턴스화합니다. 즉, 해당 선언을 처리하는 데 필요한 템플릿을 인스턴스화합니다. 그러나 즉각적인 정의는 아닙니다. 즉, 이니셜 라이더는 인스턴스화되지 않으며 해당 정적 데이터 구성원의 유형의 생성자는 암시 적으로 정의되지 않습니다 (사용 된 것으로 표시).
즉, 위의 코드는 아직 아무것도 출력하지 않을 것입니다. 지금 정적 데이터 구성원의 암시 적 인스턴스화를 일으 봅시다.
int main() {
A<1>::c; // reference them
A<2>::c;
}
이로 인해 두 정적 데이터 구성원이 존재하게되지만 문제는 - 초기화 순서는 어떻게됩니까? 간단한 독서에서는 그렇게 생각할 수 있습니다 3.6.2/1
(나에 의한 강조) : 적용 : :
"네임 스페이스 범위로 정의 된 정적 스토리지 지속 시간이있는 객체 번역 장치 동적으로 초기화 된 것은 해당 정의가 번역 장치에 나타나는 순서로 초기화되어야한다. "
이제 USENET 게시물에서 말했듯이 설명했습니다 이 결함 보고서에서,이 정적 데이터 구성원은 번역 장치에 정의되지 않지만 인스턴스화 장치, 설명 된 바와 같이 2.1/1
:
각 번역 된 번역 장치는 필요한 인스턴스화 목록을 생성하도록 검사됩니다. [참고 : 여기에는 명시 적으로 요청 된 인스턴스화가 포함될 수 있습니다 (14.7.2). ] 필요한 템플릿의 정의가 있습니다. 이러한 정의를 포함하는 번역 장치의 소스를 사용할 수 있는지 여부는 구현됩니다. [참고 : 구현은 소스가 여기에서 필요하지 않도록 충분한 정보를 번역 된 번역 장치로 인코딩 할 수 있습니다. ] 모든 필요한 인스턴스화는 인스턴스화 단위를 생성하기 위해 수행됩니다. [참고 : 이들은 번역 된 번역 단위와 유사하지만 끊임없는 템플릿 및 템플릿 정의에 대한 참조는 포함되어 있지 않습니다. ] 인스턴스티브가 실패하면 프로그램이 잘못 형성됩니다.
그러한 인스턴스화 지점은 인스턴스화와 해당 번역 단위 사이의 문맥 연결이기 때문에 그러한 회원의 인스턴스화의 요점은 실제로 중요하지 않습니다. 14.6.4.1
, 그리고 각각의 인스턴스화 지점은 인스턴스베이션에 하나의 정의 규칙에 지정된 것과 동일한 의미를 부여해야합니다. 3.2/5
, 마지막 총알).
우리가 순서 초기화를 원한다면, 우리는 인스턴스화를 엉망으로 만들지 않고 명시적인 선언을 마련해야합니다. 이것은 명시 적 전문 분야의 영역입니다. 이것은 정상 선언과는 크게 다르지 않기 때문입니다. 실제로 C ++ 0X는 3.6.2
다음에 :
정적 저장 시간을 갖는 비 국소 객체의 동적 초기화는 주문되거나 순서가 변하지 않습니다. 명시 적으로 전문화 된 클래스 템플릿의 정의 정적 데이터 구성원이 초기화를 주문했습니다. 다른 클래스 템플릿 정적 데이터 멤버 (즉, 암시 적 또는 명시 적으로 인스턴스화 된 전문화)는 정렬되지 않은 초기화를 가지고 있습니다.
이것은 코드를 의미합니다.
[1]
그리고[2]
주석 : 정적 데이터 구성원에 대한 언급이 없으므로 해당 정의가 존재하지 않기 때문에 인스턴스화가 필요하지 않기 때문에 선언이 아닙니다.B<int>
) 인스턴스화되지 않습니다. 부작용이 발생하지 않습니다.[1]
무관 :B<int>::getB()
그 자체로 사용되는 사용됩니다B<int>::mB
, 해당 정적 멤버가 존재해야합니다. 문자열은 메인 이전에 초기화됩니다 (해당 명령문 이전의 경우, 비 국한 객체를 초기화하는 일부). 사용하는 것이 없습니다B<int>::mInit
, 그래서 그것은 인스턴스화되지 않았으므로B<int>::InitHelper
생성자가 사용되지 않도록 만들어지면서 무언가를 할당하지 않을 것입니다.B<int>::mB
: 빈 문자열을 출력합니다.[1]
그리고[2]
타의 추종을 불허하는 : 이것이 당신에게 효과가 있다는 것은 행운입니다 (또는 반대입니다 :)). 위에서 설명한대로 특정 초기화 호출 순서에 대한 요구 사항은 없습니다. VC ++에서 작동하고 GCC에서 실패하고 Clang에서 작업 할 수 있습니다. 우리는 모른다.[1]
댓글[2]
무능력 : 같은 문제 - 다시, 둘 다 정적 데이터 구성원입니다 사용된:B<int>::mInit
사용합니다B<int>::getHelper
, 그리고 인스턴스화B<int>::mInit
생성자가 인스턴스화되며 사용됩니다.B<int>::mB
-하지만 컴파일러의 경우이 특정 실행에서 순서가 다릅니다 (지정되지 않은 동작은 다른 실행마다 일관성이 없어야합니다) : 초기화됩니다.B<int>::mInit
먼저, 제조되지 않은 문자열 객체에서 작동합니다.
다른 팁
문제는 정적 멤버 변수에 대해주는 정의도 템플릿이라는 것입니다.
template<class T>
std::string B<T>::mB;
template<class T>
typename B<T>::InitHelper B<T>::mInit;
편집하는 동안 T는 알려지지 않았기 때문에 실제로는 아무것도 정의하지 않습니다. 클래스 선언 또는 템플릿 정의와 같은 것이므로 컴파일러는 볼 때 코드 또는 예약 저장소를 생성하지 않습니다.
정의는 템플릿 클래스를 사용할 때 나중에 암시 적으로 발생합니다. Segfaulting의 경우 사용하지 않기 때문에 bu003Cint> :: Minit, 그것은 결코 만들어지지 않습니다.
솔루션은 필요한 멤버를 명시 적으로 정의하는 것입니다 (초기화하지 않고) : 소스 파일을 어딘가에 넣습니다.
template<>
typename B<int>::InitHelper B<int>::mInit;
이것은 기본적으로 템플릿 클래스를 명시 적으로 정의하는 것과 같은 방식으로 작동합니다.
1] 무관 한 사례 : 괜찮습니다.
static InitHelper B<int>::mInit
존재하지 않는다. 템플릿 클래스 (struct)의 멤버가 사용되지 않으면 컴파일되지 않습니다.1] 및 [2] 무의미한 사례 : 괜찮습니다.
B<int>::getHelper()
사용static InitHelper B<int>::mInit
그리고mInit
존재합니다.1]은 언급했다.