2013-12-10 2 views
3

그래서 저는 최근에 멤버 함수 데이터에 대한 포인터를 저장하는 기능을 추가 한 변형 클래스가 있습니다. 다음 코드를 사용하여이를 수행합니다.member-function-pointer 잘못된 해킹에 대한 대안?

class Variant 
{ 
    void* _value; 

    template <typename T1> 
    void Variant::ThisCall(T1* pThis) 
    { 
     typedef void(T1::* fptr)(); 
     fptr a; 
     int* iiVal = new (&a) int; 
     *iiVal = *((int*)_value); 
     (pThis->*a)(); 
    } 
}; 

// usage 
Variant myfunc = &SomeClass::SomeMethod; 
SomeClass* s = new SomeClass(); 
myfunc.ThisCall(s); 

이 솔루션에서 가장 큰 점은 멤버 함수 포인터를 void *로 캐스트 할 수 없다는 것입니다. 따라서 대입 연산자는 대개이 연산의 역함수를 수행합니다. 주어진 데이터를 가져 와서 int 포인터 (포인터 자체 인 경우)로 마스크하고 int 포인터를 void *에 할당합니다. 이것은 완벽하게 유효합니다.

내 질문은 다음과 같습니다. 왜 이것이 문제에 대한 끔찍한 해결책이라고 생각합니까? 나는 이것이 몇 가지 심각한 문제가 될만한 큰 해킹 인 것처럼 느낀다. 그러나 나는 과거를 볼 수 없기 때문에 며칠 동안이 문제에 깊이 빠져 있었다. 감사!

[EDIT # 1]

한 주석이 가상 방식으로 작동하지 않을 수도 있다는 것을 유의. 나는 다음 코드를 사용하여 테스트했으며 체크 아웃 한 것으로 보인다.

class ImplA : public Base 
{ 
public: 
    virtual void Print() 
    { 
     cout << "ImplA print\n"; 
    } 
}; 

class ImplB : public Base 
{ 
public: 
    virtual void Print() 
    { 
     cout << "ImplB print\n"; 
    } 
}; 

class ImplC : public ImplA 
{ 
public: 
    virtual void Print() 
    { 
     cout << "ImplC print\n"; 
    } 
}; 

// usage 
Variant x = &Base::Print; 
auto b = new ImplA; // replace ImplA with ImplB or ImplC and it works as expected 
x.ThisCall(b); 

몇 가지 추가 정보를 보려면 VS2010을 제 컴파일러로 사용하고 있습니다. 감사!

[편집 # 2]

이 컨텍스트를 제공하기 위해, 내가 지금하는 동안 짧은이 변형 클래스에 노력하고있다 그리고 당신이 그것에 던질 수있는 것을 지원하기 위해 노력하고 있습니다. 이렇게, 나는 함수 포인터 및 멤버 함수 포인터에 대해 생각했다. 그런 다음이 솔루션을 실제로 생각해 냈습니다. 캐스팅과 구문은 나를위한 첫 번째 붉은 깃발 이었지만, 그것이 보유하고있는 데이터의 분산 때문에, 영토와 함께 온다고 생각했습니다. 그러나 이것이 아직도 어떻게 작동하는지 확신 할 수 없습니다.

+1

* 한 가지 문제 : * [가상 멤버 함수에 대한 포인터] (http://coliru.stacked-crooked.com/a/4691b73521b8145e)와 함께 작동하지 않을 가능성이 있습니다. 'void *'는 * 어떤 * 함수 포인터 IIRC와 같은 크기를 가질 필요는 없다. – dyp

+1

IIRC 캐스팅 함수 포인터가 유효하지 않으며 UB로 연결됩니다. – RedX

+0

@DyP : 방금 가상 멤버 함수를 확인했는데 제대로 작동합니다. 나는 그걸 증명할 때 사용했던 코드를 게시 할 것이다. 그래도 고마워! –

답변

1

무시 자체가 코드를 불법 만드는 앨리어싱 위반, 당신이하고있는 것은이하는 것과 같습니다

typedef void(T1::* fptr)(); 
    fptr a; 
    memcpy(&a, _value, sizeof(int)); 
    (pThis->*a)(); 

이 아닌 휴대용 이유가 명백해야한다; fptr의 크기가 int과 같기 때문에 저장소를 부분적으로 초기화하거나 오버플로 할 가능성이 있습니다.당신이 sizeof(fptr)sizeof(int)을 대체하고, fptr 포함하기에 충분한 크기에 저장 그 _value 점을 보장하는 경우

이 법적 될 것이다. 그러나 앨리어싱 대신 여전히 memcpy을 사용해야합니다. memcpy은 작동이 보장되며 (3.9p2) 별칭을 지정하면 일반적으로 최적화 상태에서 동작을 표시하거나 검색을 변경하기 어려운 버그가 발생할 수 있습니다.

+0

나는 대답을 좋아합니다. 이 질문에 정말로주의를 기울이는 유일한 사람. 감사. –

0

주석에서 언급 한 것처럼이 코드는 이동성이 뛰어나며 안전하지 않습니다. 그냥 포인터를 저장하는 경우 나 표준 : 기능을 사용하는 것이 좋습니다 또는 부스트 : : 함수 래퍼 것 작동하려면 :

template <typename T> 
class Variant { 
    std::function<void(T*)> fun; 
public: 
    Variant(void (T:: *ptr)()) : fun(ptr) { 
    } 

    void ThisCall(T* pThis) { 
     fun(pThis); 
    } 
}; 

Variant<SomeClass> myfunc = &SomeClass::SomeMethod; 
SomeClass* s = new SomeClass(); 
myfunc.ThisCall(s); 

을하지만 당신은 정말 아무것도 저장하려면 왜 그냥 부스트 : 어떤 라이브러리를 사용할 수 있습니까?

class VariantWithAny { 
    boost::any val; 
public: 
    VariantWithAny() {} 

    VariantWithAny(const boost::any& val) : val(val) {} 

    VariantWithAny& operator=(const boost::any& val) { 
     this->val = val; 
     return *this; 
    } 

    template <typename T> 
    void ThisCall(T* pThis) { 
     typedef void (T::* fptr)(); 
     fptr a = boost::any_cast<fptr>(val); 
     (pThis->*a)(); 
    } 
}; 

VariantWithAny myfunc2(&SomeClass::SomeMethod1); 
myfunc2 = &SomeClass::SomeMethod2; 
SomeClass* s2 = new SomeClass(); 
myfunc2.ThisCall(s2); 

boost :: any_cast는 안전하며 유형이 일치하지 않으면 예외 (boost :: bad_any_cast)가 발생합니다.

[편집] : boost :: any를 사용하는 트릭은 순수 가상 자리 표시 자에서 상속 한 템플릿 홀더 클래스에 값을 저장하는 것입니다. 그것이 거의 모든 가치를 지닐 수있는 방법이며, 그것을 (무효 *)에 던질 필요가 없습니다. 구현을 확인하십시오. 매우 작은 파일입니다.

+0

기존 구현을 래핑하려는 경우 왜 고유 한 변형을 작성해야합니까? –

+0

사용중인 인터페이스와 일치하는 것은 단지 래퍼입니다. ThisCall 메서드가 필요하지 않다면 boost :: any를 직접 사용할 수 있습니다. –