2013-03-08 77 views
0

要隱藏該接口的用戶實現的細節,以避免廣泛使用的模板化的功能,我認爲以下概念:演員指針到模板類

// data.h

#ifndef DATA_H_ 
#define DATA_H_ 

#include <cstddef> 

template <size_t N = 0> 
class Data 
{ 
    public: 
     const size_t n; 
     size_t values[N]; 
     Data<N>(); 
}; 

#endif // DATA_H_ 

// data.cpp

#include "data.h" 

template <size_t N> Data<N>::Data() 
: 
    n(N), 
    values() 
{ 
    for (size_t i = 0; i < n; ++i) 
    { 
     values[i] = i; 
    } 
} 

template class Data<1u>; 
template class Data<2u>; 

// list.h

#ifndef LIST_H_ 
#define LIST_H_ 

#include <cstddef> 
#include <memory> 

class List 
{ 
    private: 
     std::shared_ptr<void> data; 
    public: 
     List(const size_t); 
     void printData() const; 
}; 

#endif // LIST_H_ 

// list.cpp

#include "list.h" 

#include <iostream> 
#include <stdexcept> 

#include "data.h" 

List::List(const size_t n) 
: 
    data() 
{ 
    switch (n) 
    { 
     case 1u: 
     data = std::static_pointer_cast<void>(std::make_shared<Data<1u>>()); 
     break; 
     case 2u: 
     data = std::static_pointer_cast<void>(std::make_shared<Data<2u>>()); 
     break; 
     default: 
     throw std::runtime_error("not instantiated.."); 
    } 
} 

void List::printData() const 
{ 
    auto obj = std::static_pointer_cast<Data<>>(data); // my question is about this 
    std::cout << obj->n << ": "; 
    for (size_t i = 0; i < obj->n; ++i) 
    { 
     std::cout << obj->values[i] << " "; 
    } 
    std::cout << "\n"; 
} 

// main.cpp中

#include "list.h" 

int main() 
{ 
    for (size_t i = 1; i <= 2; ++i) 
    { 
     try 
     { 
      List list(i); 
      list.printData(); 
     } 
     catch (...) 
     { 
      return 1; 
     } 
    } 
} 

我知道,有些人可能認爲這是可怕的設計。請不要在這裏討論這個,除非你有一個很好的選擇。

我的問題是關於行auto obj = std::static_pointer_cast<Data<>>(data);List::printData()。這感覺有點不安全。是否存在保證是否使用了正確的實例? g++-4.6.3不會對此代碼給出警告,並且它會打印預期值。

+0

我希望你認識到你的數據類中的'const size_t n'是完全不需要的。您將該值作爲模板參數列表的一部分,即**'N' **。 – WhozCraig 2013-03-08 18:01:05

+0

@WhozCraig怎麼樣'obj-> n'?如何用模板參數做到這一點? – stefan 2013-03-08 18:02:16

+0

std :: static_pointer_cast >將始終投射到數據<0> ...是你在問什麼? – 2013-03-08 18:02:24

回答

2

是的。這是不安全的。任何時候通過void*投下你都會冒UB的危險。編譯器沒有警告你,因爲它不再需要這樣做的類型信息。因此,你可以投射到你沒有做的正確類型。

從技術上講,你在這裏造成未定義的行爲。然而,我敢打賭,它通常會起作用。這與你一直在C中做的一些蠢事無關。

它會工作的原因是您的實例的二進制佈局可能會相同。首先是'n',如果你正在做這個討厭的技巧,你需要做的是,然後是數組的開始。

如果你曾經在指針領域之外做過這件事,那麼你就會搞砸自己。

對象被正確刪除的唯一原因是shared_ptr在創建時創建了一個默認的刪除器,因此它知道如何刪除正確的類型。如果您嘗試過這些,其他任何智能指針都會導致各種類型的BS。

編輯:

現在,這樣做的更好的方法是使用類型系統的陣列放棄大小。你真的想要一個運行時分配的數組,使用運行時系統來創建它!無論如何,你在免費商店創建它,所以你沒有從濫用類型系統中獲得任何好處。如果您只是根據傳遞給列表構造函數的大小分配數組,則可以具有安全的,可預測的標準行爲。

+0

如果它與C99一樣,將會有內存佈局保證,允許你通過不同的類型訪問領先的公共字段(對'struct sockaddr'和朋友很有用),但我不是當然,這是保證爲數組工作(更不用說長度爲零的數組)。在任何情況下,我強烈懷疑爲零長度的數組下標是UB(除非C++將它視爲C99的可變長度數組)。 – 2013-03-09 01:13:29

+0

@tc - 是的,我也不知道。我懷疑C++把它當作一個可變長度的數組。事實上,在C++中有比C更多的數組訪問限制(比如整個&array [one_past_end])。無論如何,這是對類型系統的嚴重濫用,並且必然會造成問題...只要團隊成員在看到它時不斷地進入WTF 0_o。我當然不會鼓勵它,但我認爲它通常會起作用,通常會起作用。 – 2013-03-09 01:33:28

+0

我也沒有看到它是如何實用的,因爲你必須在編譯時知道所有需要的列表大小...... – 2013-03-09 01:36:04

0

你在那裏有一個static_cast在編譯時被解析。那麼,你告訴編譯器:

auto obj = std::static_pointer_cast<Data<>>(data); 

static_pointer_cast變量data鍵入std::shared_ptr<Data<>>
默認情況下(如您在模板原型中聲明的)Data<>表示Data<0>

所以你總是會得到相同類型的shared_pointer

你可以做什麼,是做一個接口,並在運行時獲得大小。

class IData 
{ 
    virutal size_t GetDataSize() = 0; 
} 

template <size_t N = 0> 
class Data : public IData 
{ 
    public: 
     const size_t n; 
     size_t values[N]; 
     Data<N>(); 
     virtual size_t GetDataSize() override { return N; } 
}; 

然後按住列表的接口類型,只是使用data->GetDataSize();

此外,不要把模板實施.cpp文件,他們需要看到他們使用的地方。

+0

如果這是它唯一使用的地方,那麼將模板實現放在cpp文件中是非常合適的。 – 2013-03-08 18:17:49

+0

@CrazyEddie Yeap,但它不是它在這種情況下使用的地方。 – 2013-03-08 18:22:51