2017-07-26 35 views
0

我目前正在將數據存儲庫集成到我的應用程序中。我需要能夠爲我的單元測試嘲笑這個數據存儲(這是I/O密集型),因此創建了該庫接口的包裝。靜態地包裝庫的多態迭代器而不將庫暴露給用戶

不幸的是,在它的接口中,這個庫將迭代器作爲指針而不是值返回,因爲它們在運行時是多態的。

我的問題是,由於多態性我加入的層,似乎不可避免地添加是多態運行時的迭代器,因此招致間接的一個新的水平和一些動態分配...

// Library code 
class LibIterator 
{ 
    // pure virtual methods 
}; 

class LibDataStore 
{ 
    LibIterator* getIt(); 
}; 

// My interface 
class IMyIterator{ 
    // pure virtual methods 
}; 

class MyLibIterator : public IMyIterator 
{ 
    std::unique_ptr<LibIterator> m_iterator; 
}; 

class MyIterator 
{ 
    std::unique_ptr<MyLibIterator> m_iterator; 
}; 

class IMyDataStore 
{ 
    MyIterator getIt(); 
}; 

這在每次使用迭代器的任何方法的是一個可怕的很多指針取消引用,虛擬調度,再加上至少2次動態分配(的lib迭代器+礦)爲每個迭代器創建...

我正在考慮使用CRTP來幫助解決這個問題,但我無法想出一種方法來防止使用IMyDataStore查看通過MyIterator的類型出血的迭代器的具體實現。

有沒有我可能錯過的技巧?

回答

1
template<class T, std::size_t sz, std::size_t algn> 
struct poly { 

,如果你不害怕但你應該

poly_vtable<T> const* vtable=0; 
    std::aligned_storage_t<sz, algn> data; 

我們以後可以覆蓋虛函數表。

T* get() { return vtable->get(&data); } 
    T const* get() const { return vtable->get((void*)&data); } 

示例使用vtable。下面是設置:

template<class U, class...Args> 
    U* emplace(Args&&...args){ 
    static_assert(sizeof(U)<=sz && alignof(U)<=algn, "type too large"); 
    clear(); 
    U* r = ::new((void*)&data) U(std::forward<Args>(args)...); 
    vtable = get_poly_vtable<T,U>(); 
    return r; 
    } 

副本:

poly(poly const& o){ 
    if (!o.vtable) return; 
    o.vtable->copy(&data, &o.data); 
    vtable=o.vtable; 
    } 
    poly(poly&& o){ 
    if (!o.vtable) return; 
    o.vtable->move(&data, &o.data); 
    vtable=o.vtable; 
    } 
    poly& operator=(poly const& rhs) { 
    if (this == &rhs) return *this; 
    clear(); 
    if (!rhs.vtable) return *this; 
    rhs.vtable->copy(&data, &rhs.data); 
    vtable = rhs.vtable; 
    return *this; 
    } 
    poly& operator=(poly&& rhs) { 
    if (this == &rhs) return *this; 
    clear(); 
    if (!rhs.vtable) return *this; 
    rhs.vtable->move(&data, &rhs.data); 
    vtable = rhs.vtable; 
    return *this; 
    } 

破壞:

void clear(){ 
    if (!vtable) return; 
    vtable->dtor(&data); 
    vtable=nullptr; 
    } 
    ~poly(){clear();} 

指針等的操作:從T中派生的類型

explicit operator bool()const{return vtable;} 
    T& operator*(){ return *get();} 
    T const& operator*() const{ return *get();} 
    T* operator->(){ return get();} 
    T const* operator->() const{ return get();} 

構建體:

template<class U, 
    class dU=std::decay_t<U>, 
    class=std::enable_if_t<!std::is_same<dU, poly>{}>, 
    class=std::enable_if_t<std::is_base_of<T, dU>{}> 
    > 
    poly(U&& u) { 
    emplace<std::decay_t<U>>(std::forward<U>(u)); 
    } 
}; 

請注意,此類型時const指const值。

這個想法是,poly<T>T類型的多態值。它有大小限制。

您可以使用T* vtable來安排其他操作的多態性。

template<class T> 
struct poly_vtable{ 
    T*(*get)(void*)=0; 
    void(*copy)(void*,void const*)=0; 
    void(*move)(void*,void*)=0; 
    void(*dtor)(void*)=0; 
}; 

template<class T, class U> 
poly_vtable<T> make_poly_vtable() { 
    return { 
     [](void* ptr)->T*{ return static_cast<U*>(ptr); }, 
     [](void* dest, void const* src){ ::new(dest) U(*static_cast<U const*>(src)); }, 
     [](void* dest, void* src){ ::new(dest) U(std::move(*static_cast<U*>(src))); }, 
     [](void* ptr){ static_cast<U*>(ptr)->~U(); } 
    }; 
} 
template<class T, class U> 
poly_vtable<T> const* get_poly_vtable() { 
    static const auto r = make_poly_vtable<T,U>(); 
    return &r; 
} 

get_poly_vtable<T,U>()返回指向靜態局部poly_vtable<T>與實現的每個操作。

Live example

現在你可以有一個基於vtable的多態值類型。

相同的技術可以擴展到更多的操作;簡單地轉換爲基礎,並使用真正的vtables更容易。

使用這個,你存儲一個poly<IMyIterator, 64, alignof(IMyIterator)>。這是一個包含64字節緩衝區的值類型。


的另一種方法,以減少間接將與可能的重複範圍探視替換每個項目的探視的概念。

如果每回調一次訪問10個項目的範圍,則調用虛擬方法的開銷比每個回調的開銷少10倍。

您可以使用範圍對象創建輸入迭代器,範圍對象最多可包含10個緩衝區項,並在到達結尾時自動重建它,如果有更多可用的批量獲取數據。

+0

是的,這個就地手卷虛擬表的實現確實讓我害怕:) 我想我會繼續與我目前的實施工作,如果基準測試表明,單個迭代器解引用增加了太多的開銷,我按照你的建議緩衝結果。 或者也許我仍然通過封裝來讓太多的底層實現顯示,我應該找到更好的抽象來處理這個問題。 – AliaumeM