2010-11-23 5 views
6

"variant-type 개체"를 구현하는 데 수년 동안 성공적으로 사용해온 코드가 있습니다. 즉, 다양한 유형의 값을 보유 할 수 있지만 가장 큰 유형의 메모리 만 사용합니다 (대략). 이 코드는 비 POD 데이터 유형을 지원한다는 점을 제외하고는 태그가 붙은 유니온과 유사합니다. char 버퍼, placement new/delete 및 reinterpret_cast <>을 사용하여이 마법을 달성합니다. 게재 위치 - 새로운 vcc gcc 4.4.3 엄격한 앨리어싱 규칙

내가 최근에 (-03 및 -Wall 포함) GCC 4.4.3에서이 코드를 컴파일 시도하고,이 같은 경고를 많이 가지고 : 내가 읽은 바로는

warning: dereferencing type-punned pointer will break strict-aliasing rules 

을,이 표시는 gcc의 새로운 옵티 마이저가 '버그가있는'코드를 생성 할 수 있다고 생각합니다. 분명히 피하고 싶습니다.

아래 코드의 '장난감 버전'을 붙여 넣었습니다. 비 -POD 데이터 유형을 계속 지원하면서 gcc 4.4.3에서 코드를 안전하게 만들 수있는 방법이 있습니까? 필자는 최후의 수단으로 항상 -fno-strict-aliasing을 사용하여 코드를 컴파일 할 수 있다는 것을 알고 있지만, 최적화를 위반하지 않는 코드를 사용하면 좋을 것이므로 그렇게하지 않을 것입니다.

(boost/C++ 0X 솔루션이 흥미 롭기 때문에 코드베이스에 부스트 또는 C++ 0X 의존성을 도입하는 것을 피하고 싶습니다. 좀 더 구형이 선호됩니다)

#include <new> 

class Duck 
{ 
public: 
    Duck() : _speed(0.0f), _quacking(false) {/* empty */} 
    virtual ~Duck() {/* empty */} // virtual only to demonstrate that this may not be a POD type 

    float _speed; 
    bool _quacking; 
}; 

class Soup 
{ 
public: 
    Soup() : _size(0), _temperature(0.0f) {/* empty */} 
    virtual ~Soup() {/* empty */} // virtual only to demonstrate that this may not be a POD type 

    int _size; 
    float _temperature; 
}; 

enum { 
    TYPE_UNSET = 0, 
    TYPE_DUCK, 
    TYPE_SOUP 
}; 

/** Tagged-union style variant class, can hold either one Duck or one Soup, but not both at once. */ 
class DuckOrSoup 
{ 
public: 
    DuckOrSoup() : _type(TYPE_UNSET) {/* empty*/} 
    ~DuckOrSoup() {Unset();} 

    void Unset() {ChangeType(TYPE_UNSET);} 
    void SetValueDuck(const Duck & duck) {ChangeType(TYPE_DUCK); reinterpret_cast<Duck*>(_data)[0] = duck;} 
    void SetValueSoup(const Soup & soup) {ChangeType(TYPE_SOUP); reinterpret_cast<Soup*>(_data)[0] = soup;} 

private: 
    void ChangeType(int newType); 

    template <int S1, int S2> struct _maxx {enum {sz = (S1>S2)?S1:S2};}; 
    #define compile_time_max(a,b) (_maxx< (a), (b) >::sz) 
    enum {STORAGE_SIZE = compile_time_max(sizeof(Duck), sizeof(Soup))}; 

    char _data[STORAGE_SIZE]; 
    int _type; // a TYPE_* indicating what type of data we currently hold 
}; 

void DuckOrSoup :: ChangeType(int newType) 
{ 
    if (newType != _type) 
    { 
     switch(_type) 
     { 
     case TYPE_DUCK: (reinterpret_cast<Duck*>(_data))->~Duck(); break; 
     case TYPE_SOUP: (reinterpret_cast<Soup*>(_data))->~Soup(); break; 
     } 
     _type = newType; 
     switch(_type) 
     { 
     case TYPE_DUCK: (void) new (_data) Duck(); break; 
     case TYPE_SOUP: (void) new (_data) Soup(); break; 
     } 
    } 
} 

int main(int argc, char ** argv) 
{ 
    DuckOrSoup dos; 
    dos.SetValueDuck(Duck()); 
    dos.SetValueSoup(Soup()); 
    return 0; 
} 
+0

이상한 코드입니다 ... –

+0

이 코드를 GCC 팀에 제출 했습니까? 버그 보고서일까요? – curiousguy

답변

1

OK, 추가 void *를 저장하려는 경우 가능합니다. 샘플을 약간 다시 포맷하여 작업하기가 더 쉬웠습니다. 이것을보고 자신의 필요에 맞는 지보십시오. 또한 유용성에 도움이되는 템플릿을 추가 할 수 있도록 샘플을 몇 개 제공했습니다. 그것들은 훨씬 더 확장 될 수 있지만 그것은 좋은 생각을 줄 것입니다.

무슨 일인지 알 수 있도록 출력물도 있습니다.

한 가지 더 알려 드리면 적절한 복사기 및 할당 연산자를 제공해야하지만이 문제의 핵심은 아니라는 것을 알고 있다고 생각합니다.

내 g ++ 버전 정보 :

그램 ++ --version g ++ (SUSE 리눅스) 4.5.0 20100604 [GCC-4_5-가지 버전 160292]

#include <new> 
#include <iostream> 

class Duck 
{ 
public: 
    Duck(float s = 0.0f, bool q = false) : _speed(s), _quacking(q) 
    { 
    std::cout << "Duck::Duck()" << std::endl; 
    } 
    virtual ~Duck() // virtual only to demonstrate that this may not be a POD type 
    { 
    std::cout << "Duck::~Duck()" << std::endl; 
    } 

    float _speed; 
    bool _quacking; 
}; 

class Soup 
{ 
public: 
    Soup(int s = 0, float t = 0.0f) : _size(s), _temperature(t) 
    { 
    std::cout << "Soup::Soup()" << std::endl; 
    } 
    virtual ~Soup() // virtual only to demonstrate that this may not be a POD type 
    { 
    std::cout << "Soup::~Soup()" << std::endl; 
    } 

    int _size; 
    float _temperature; 
}; 

enum TypeEnum { 
    TYPE_UNSET = 0, 
    TYPE_DUCK, 
    TYPE_SOUP 
}; 
template < class T > TypeEnum type_enum_for(); 
template < > TypeEnum type_enum_for<Duck>() { return TYPE_DUCK; } 
template < > TypeEnum type_enum_for<Soup>() { return TYPE_SOUP; } 

/** Tagged-union style variant class, can hold either one Duck or one Soup, but not both at once. */ 
class DuckOrSoup 
{ 
public: 
    DuckOrSoup() : _type(TYPE_UNSET), _data_ptr(_data) {/* empty*/} 
    ~DuckOrSoup() {Unset();} 

    void Unset() {ChangeType(TYPE_UNSET);} 
    void SetValueDuck(const Duck & duck) 
    { 
    ChangeType(TYPE_DUCK); 
    reinterpret_cast<Duck*>(_data_ptr)[0] = duck; 
    } 
    void SetValueSoup(const Soup & soup) 
    { 
    ChangeType(TYPE_SOUP); 
    reinterpret_cast<Soup*>(_data_ptr)[0] = soup; 
    } 

    template < class T > 
    void set(T const & t) 
    { 
    ChangeType(type_enum_for<T>()); 
    reinterpret_cast< T * >(_data_ptr)[0] = t; 
    } 

    template < class T > 
    T & get() 
    { 
    ChangeType(type_enum_for<T>()); 
    return reinterpret_cast< T * >(_data_ptr)[0]; 
    } 

    template < class T > 
    T const & get_const() 
    { 
    ChangeType(type_enum_for<T>()); 
    return reinterpret_cast< T const * >(_data_ptr)[0]; 
    } 

private: 
    void ChangeType(int newType); 

    template <int S1, int S2> struct _maxx {enum {sz = (S1>S2)?S1:S2};}; 
    #define compile_time_max(a,b) (_maxx< (a), (b) >::sz) 
    enum {STORAGE_SIZE = compile_time_max(sizeof(Duck), sizeof(Soup))}; 

    char _data[STORAGE_SIZE]; 
    int _type; // a TYPE_* indicating what type of data we currently hold 
    void * _data_ptr; 
}; 

void DuckOrSoup :: ChangeType(int newType) 
{ 
    if (newType != _type) 
    { 
     switch(_type) 
     { 
     case TYPE_DUCK: (reinterpret_cast<Duck*>(_data_ptr))->~Duck(); break; 
     case TYPE_SOUP: (reinterpret_cast<Soup*>(_data_ptr))->~Soup(); break; 
     } 
     _type = newType; 
     switch(_type) 
     { 
     case TYPE_DUCK: (void) new (_data) Duck(); break; 
     case TYPE_SOUP: (void) new (_data) Soup(); break; 
     } 
    } 
} 

int main(int argc, char ** argv) 
{ 
    Duck sample_duck; sample_duck._speed = 23.23; 
    Soup sample_soup; sample_soup._temperature = 98.6; 
    std::cout << "Just saw sample constructors" << std::endl; 
    { 
    DuckOrSoup dos; 
    std::cout << "Setting to Duck" << std::endl; 
    dos.SetValueDuck(sample_duck); 
    std::cout << "Setting to Soup" << std::endl; 
    dos.SetValueSoup(sample_soup); 
    std::cout << "Should see DuckOrSoup destruct which will dtor a Soup" 
     << std::endl; 
    } 
    { 
    std::cout << "Do it again with the templates" << std::endl; 
    DuckOrSoup dos; 
    std::cout << "Setting to Duck" << std::endl; 
    dos.set(sample_duck); 
    std::cout << "duck speed: " << dos.get_const<Duck>()._speed << std::endl; 
    std::cout << "Setting to Soup" << std::endl; 
    dos.set(sample_soup); 
    std::cout << "soup temp: " << dos.get_const<Soup>()._temperature << std::endl; 
    std::cout << "Should see DuckOrSoup destruct which will dtor a Soup" 
     << std::endl; 
    } 
    { 
    std::cout << "Do it again with only template get" << std::endl; 
    DuckOrSoup dos; 
    std::cout << "Setting to Duck" << std::endl; 
    dos.get<Duck>() = Duck(42.42); 
    std::cout << "duck speed: " << dos.get_const<Duck>()._speed << std::endl; 
    std::cout << "Setting to Soup" << std::endl; 
    dos.get<Soup>() = Soup(0, 32); 
    std::cout << "soup temp: " << dos.get_const<Soup>()._temperature << std::endl; 
    std::cout << "Should see DuckOrSoup destruct which will dtor a Soup" 
     << std::endl; 
    } 
    std::cout << "Get ready to see sample destructors" << std::endl; 
    return 0; 
} 
+0

BTW를 사용하면 오버로드를 통해 형식을 변경할 때 (그리고 더 많은 템플릿을 사용하여) 형식을 변경할 때 기본 ctor를 제거 할 수 있습니다. 또는 매개 변수를 사용하여 생성하고 형식을 전환 할 때 초기 기본 ctor 오버 헤드를 저장하십시오. –

+0

필요에 따라 해당 void 포인터를 "즉석에서"다시 작성할 수있는 이유는 무엇입니까? void 포인터를 멤버 변수로 저장하는 유일한 이유는 컴파일러의 경고 생성기를 능가하는 것이므로 항목 당 발생하는 매우 만족스러운 이유는 아닙니다. 런타임 패널티. –

0

나는 즉, void * 일시적를 사용하여 불평하지 (-Wstrict-aliasing=2 실행 4.2.4) GCC를 설득하는 데 성공했습니다.

void SetValueDuck(const Duck & duck) {ChangeType(TYPE_DUCK); void *t=&_data; reinterpret_cast<Duck*>(t)[0] = duck;} 
+0

gcc 4.4.3은 여전히 ​​위의 변경 사항으로 경고합니다 (엄격한 앨리어싱 규칙을 위반하는 익명 포인터에 대한 경고는 제외). –

0

는 아직도의 필요성이나 사용법을 이해할 수없는이 있지만 g ++ -03 -Wall와 4.4.3 다음 패치와 함께 작동합니다. 작동하는 경우 유스 케이스를 공유 할 수 있습니까? 이유가 무엇입니까?

class DuckOrSoup 
{ 
public: 
    DuckOrSoup() : _type(TYPE_UNSET) {_duck = NULL; _soup = NULL;/* empty*/} 
    ~DuckOrSoup() {Unset();} 

    void Unset() {ChangeType(TYPE_UNSET);} 
    void SetValueDuck(const Duck & duck) {ChangeType(TYPE_DUCK); _duck = new (&_data[0])Duck (duck); } 
    void SetValueSoup(const Soup & soup) { ChangeType(TYPE_SOUP); _soup = new (&_data[0])Soup (soup); } 

private: 
    void ChangeType(int newType); 

    template <int S1, int S2> struct _maxx {enum {sz = (S1>S2)?S1:S2};}; 
    #define compile_time_max(a,b) (_maxx< (a), (b) >::sz) 
    enum {STORAGE_SIZE = compile_time_max(sizeof(Duck), sizeof(Soup))}; 

    char _data[STORAGE_SIZE]; 
    int _type; // a TYPE_* indicating what type of data we currently hold 
    Duck* _duck; 
    Soup* _soup; 
}; 

void DuckOrSoup :: ChangeType(int newType) 
{ 
    if (newType != _type) 
    { 
     switch(_type) 
     { 
     case TYPE_DUCK: 
      _duck->~Duck(); 
      _duck = NULL; 
      break; 
     case TYPE_SOUP: 
      _soup->~Soup(); 
      _soup = NULL; 
      break; 
     } 
     _type = newType; 
     switch(_type) 
     { 
     case TYPE_DUCK: _duck = new (&_data[0]) Duck(); break; 
     case TYPE_SOUP: _soup = new (&_data[0]) Soup(); break; 
     } 
    } 
} 
+0

왜 여러 종류의 데이터를 단일 객체에 저장해야합니까? 같은 이유로 다른 사람들은 boost :: variant를 사용합니다. 다만 프로그램을 부스트에 의존하게하고 싶지는 않습니다. –

+0

위의 코드는 경고없이 컴파일되지만 개체 생성자를 두 번 (ChangeType()에서 한 번, SetValue *()에서 다시 호출 함), 각 DuckOrSoup 개체에 N 개의 추가 포인터 필드를 추가합니다. 두 가지 이상의 가능한 데이터 형식을 지원하는 내 비 장난감 코드에서) –

1

내가 가진 것 다음과 같이 코드를 작성하십시오 :

typedef boost::variant<Duck, Soup> DuckOrSoup; 

그러나 나는 모두가 자신의 취향을 가지고 있다고 생각합니다.

그런데 코드가 버그가 있고 가능한 정렬 문제를 처리하지 못했기 때문에 메모리에 임의의 지점에 객체를 놓을 수 없으며 모든 유형으로 변경되는 존중해야하는 제약이 있습니다. C++ 0x에는이를 얻기위한 alignof 키워드와 정렬 된 스토리지를 가져 오는 몇 가지 유틸리티가 있습니다.

관련 문제