2011-11-03 39 views
3

我需要一種方法來驗證編譯期間指向另一個類(派生或基地)的指針的upcast/downcast不會更改指針值。也就是說,演員陣容相當於reinterpret_cast如何在編譯時驗證reinterpret_cast的有效性

具體來說,情況如下:我有一個Base類和一個Derived類(顯然來自Base)。還有一個模板Wrapper類,它由指向作爲模板參數指定的類的指針組成。

class Base 
{ 
    // ... 
}; 

class Derived 
    :public Base 
{ 
    // ... 
}; 

template <class T> 
class Wrapper 
{ 
    T* m_pObj; 
    // ... 
}; 

在一些情況下,我有Wrapper<Derived>類型的變量,我想打電話接收RO Wrapper<Base>一個(常數)參照的功能。顯然這裏沒有自動投射,Wrapper<Derived>不是從Wrapper<Base>派生。

void SomeFunc(const Wrapper<Base>&); 

Wrapper<Derived> myWrapper; 
// ... 

SomeFunc(myWrapper); // compilation error here 

在標準C++的範圍內有辦法處理這種情況。像這樣的例子:

Derived* pDerived = myWrapper.Detach(); 

Wrapper<Base> myBaseWrapper; 
myBaseWrapper.Attach(pDerived); 

SomeFunc(myBaseWrapper); 

myBaseWrapper.Detach(); 
myWrapper.Attach(pDerived); 

但我不喜歡這個。這不僅需要一個尷尬的語法,而且還會產生一個額外的代碼,因爲Wrapper有一個不平凡的地方(正如你可能猜到的),並且我正在使用異常處理。 OTOH如果指向BaseDerived的指針是相同的(就像在這個例子中一樣,因爲沒有多重繼承) - 人們可以將myWrapper轉換爲所需的類型,並調用SomeFunc,它將工作!

因此,我已經添加了以下到Wrapper

template <class T> 
class Wrapper 
{ 
    T* m_pObj; 
    // ... 

    typedef T WrappedType; 


    template <class TT> 
    TT& DownCast() 
    { 
     const TT::WrappedType* p = m_pObj; // Ensures GuardType indeed inherits from TT::WrappedType 

     // The following will crash/fail if the cast between the types is not equivalent to reinterpret_cast 
     ASSERT(PBYTE((WrappedType*)(1)) == PBYTE((TT::WrappedType*)(WrappedType*)(1))); 

     return (TT&) *this; // brute-force case 
    } 

    template <class TT> operator const Wrapper<TT>&() const 
    { 
     return DownCast<Wrapper<TT> >(); 
    } 
}; 


Wrapper<Derived> myWrapper; 
// ... 

// Now the following compiles and works: 
SomeFunc(myWrapper); 

的問題是,在某些情況下蠻力投無效。例如,在這種情況下:

class Base 
{ 
    // ... 
}; 

class Derived 
    :public AnotherBase 
    ,public Base 
{ 
    // ... 
}; 

這裏指針到Base的值不同於Derived。因此Wrapper<Derived>不等於Wrapper<Base>

我想檢測並防止這種無效沮喪的企圖。我已經添加了驗證(如您所見),但它在運行時間中有效。也就是說,代碼將被編譯和運行,並且在運行時會在調試版本中發生崩潰(或失敗的斷言)。

這很好,但我想在編譯期間抓住這一點,並且失敗了構建。一種STATIC_ASSERT。

有沒有辦法做到這一點?

+0

也許在這裏使用'static_cast' return(TT&)* this; //蠻力案件'而不是c-cast可能會有所幫助,c-cast可能會做reinterpret_cast,這確實會破壞一切。如果兩個類共享繼承,編譯器應該用'static_cast'指向正確的地方。 – RedX

+0

@RedX:在這個特定的地方,這相當於'static_cast',因爲rwo類'Wrapper '和'Wrapped '不相關 – valdo

回答

7

簡短回答:

龍答:

提供有限的內省可以在編譯時,你可以例如(使用函數重載解析)檢測是否有B類是可訪問的基類,其他類的D.

不過就是這樣。

該標準不要求完全反省,並顯着:

  • 你不能列出一個類的(直)基類
  • 你無法知道一個類是否只有一個或幾個基類
  • 你甚至不知道基類是第一基或不

當然,有問題的對象佈局或多或少是不確定的,反正(既是C如果我沒有記錯的話,++ 11增加了用虛擬方法區分平凡佈局和類的能力,這在這裏有點幫助!)

使用Clang及其AST檢查功能,我認爲你可以寫一個專用檢查器,但是這看起來很複雜,當然完全不可移植。

因此,儘管您大膽索賠P.S.請不要回答「你爲什麼要這樣做」或「這是違反標準」。我知道這一切是什麼,我有理由這樣做。,你將不得不適應你的方式。

當然,如果我們能更全面地瞭解您對此課程的使用情況,我們可以將我們的大腦集中在一起,幫助您找到更好的解決方案。


如何實現類似的系統?

我建議,首先,一個簡單的解決方案:

  • Wrapper<T>是所有者類,不可複製,不可兌換
  • WrapperRef<U>實現通過代理現有Wrapper<T>(只要T*是兌換爲U*)並提供轉換設施。

我們將用事實,即所有的指針從UnkDisposable(這是一個重要的信息!)操縱繼承

代碼:

namespace details { 
    struct WrapperDeleter { 
    void operator()(UnkDisposable* u) { if (u) { u->Release(); } } 
    }; 


    typedef std::unique_ptr<UnkDisposable, WrapperDeleter> WrapperImpl; 
} 

template <typename T> 
class Wrapper { 
public: 
    Wrapper(): _data() {} 

    Wrapper(T* t): _data(t) {} 

    Wrapper(Wrapper&& right): _data() { 
    using std::swap; 
    swap(_data, right._data); 
    } 

    Wrapper& operator=(Wrapper&& right) { 
    using std::swap; 
    swap(_data, right._data); 
    return *this; 
    } 

    T* Get() const { return static_cast<T*>(_data.get()); } 

    void Attach(T* t) { _data.reset(t); } 
    void Detach() { _data.release(); } 

private: 
    WrapperImpl _data; 
}; // class Wrapper<T> 

現在,我們奠定了基礎,我們可以使我們的自適應代理。因爲我們只處理通過WrapperImpl一切,我們確保通過模板構造std::enable_ifstd::is_base_of的類型安全(和我們static_cast<T*>的有意義岬)通過檢查轉換:

template <typename T> 
class WrapperRef { 
public: 
    template <typename U> 
    WrapperRef(Wrapper<U>& w, 
    std::enable_if_c< std::is_base_of<T, U> >::value* = 0): 
    _ref(w._data) {} 

    // Regular 
    WrapperRef(WrapperRef&& right): _ref(right._ref) {} 
    WrapperRef(WrapperRef const& right): _ref(right._ref) {} 

    WrapperRef& operator=(WrapperRef right) { 
    using std::swap; 
    swap(_ref, right._ref); 
    return *this; 
    } 

    // template 
    template <typename U> 
    WrapperRef(WrapperRef<U>&& right, 
    std::enable_if_c< std::is_base_of<T, U> >::value* = 0): 
    _ref(right._ref) {} 

    template <typename U> 
    WrapperRef(WrapperRef<U> const& right, 
    std::enable_if_c< std::is_base_of<T, U> >::value* = 0): 
    _ref(right._ref) {} 

    T* Get() const { return static_cast<T*>(_ref.get()); } 

    void Detach() { _ref.release(); } 

private: 
    WrapperImpl& _ref; 
}; // class WrapperRef<T> 

它可能根據進行調整你的需求,例如你可以刪除複製和移動類的能力,以避免指向不再有效的Wrapper。另一方面,你也可以使用shared_ptr/weak_ptr的方法來豐富這一點,以便能夠複製和移動包裝,並仍然保證可用性(但要小心內存泄漏)。

注意:WrapperRef不提供Attach方法是故意的,這種方法不能用於基類。否則,既AppleBananaFruit派生,你可以通過WrapperRef<Fruit>即使原始Wrapper<T>Wrapper<Apple>附加Banana ...

注:這是因爲普通UnkDisposable基類的容易!這就是我們共同的分母(WrapperImpl)。

+0

非常感謝您的回答。我想到了編譯時檢查,某種表達式可能在編譯時被計算出來,並受到一個'STATIC_ASSERT'宏(希望你熟悉這種技術)。順便說一下,我使用了'PBYTE((WrappedType *)(1))== PBYTE((TT :: WrappedType *)(WrappedType *)(1))的表達式** ** ** ** ** ** ** ** **可以在編譯時計算出來。我確定在發佈版本中啓用了優化,編譯器將替換已知的結果。但是我使用的編譯器(msvc)不允許這樣做。 – valdo

+0

更寬的圖片。我有一個基礎接口'UnkDisposable',它有'virtual Release()= 0;'。 'Wrapper'是一個RAII包裝器,它將指針包裝成這樣一個對象,並確保在必要時調用Release。 'Wrapper'是一個模板類,它包裝從UnkDisposable繼承的任何類。類以不同的方式實現'UnkDisposable':一些立即刪除對象,而另一些實現引用計數(他們也有'AddRef')。繼續... – valdo

+0

有一個函數需要一些參數並負責啓動異步操作。如果成功 - 它應該獲得適當分配資源的所有權。有一個從'UnkDisposable'繼承的'CompletionHandler'接口,它添加了相關的完成/錯誤處理方法。該函數將'Wrapper &'作爲參數。如果一切正常 - 它將分配的對象從給定的包裝器中分離出來並獲得它的所有權(將它附加到它的內部包裝器中)。繼續... – valdo