2013-10-15 71 views
6

我正在尋找將元素添加到STL容器背面的常規方法。我希望代碼支持儘可能多類型的STL容器。下面這段代碼演示了我的問題:向STL容器背面添加元素

#include <vector> 
#include <string> 

using namespace std; 

template<typename T> 
class S { 
    T built; 
    typename T::iterator built_it; 
public: 
    S() : built{}, built_it{built.end()} {} 
    void add_to(typename T::value_type e) { 
    built.emplace(built_it, e); 
    ++built_it; 
    } 
    const T& get() { 
    return built; 
    } 
}; 

int main() 
{ 
    S<std::vector<int>> e; 
    S<std::string> f; 
    e.add_to(3); // works 
    f.add_to('c'); // doesn't 
} 

這裏的問題很微妙。此代碼適用於vector s,因爲std::vector實現了emplace函數。但std::string不!是否有更一般的方法來執行相同的操作?

+1

爲什麼不使用'push_back'而不是'emplace'? 'push_back'由兩者支持,似乎更好地匹配你正在嘗試做的事情。 –

+0

'push_back'不受類似'std :: list'的支持。我想這可能是一個有效的妥協。 – thirtythreeforty

+1

'push_back'由'std :: list'支持:http://www.cplusplus.com/reference/list/list/push_back/ –

回答

9

generic way(不一定是最有效的方式)是:

c.insert(c.end(), value); 

其中,當然,value需要適合於容器c (您可以使用decltype(c)::value_type)。在關聯容器的情況下,例如map,這是一個std::pair

這適用於全部標準容器除了std::forward_list。對於某些容器,元素隨後會添加到最後,因爲某些c.end()只是一個可能被忽略的提示。


作爲後續行動的意見,這裏的先進的東西;)

當你要插入已知數量的元素到給定容器c(的C型),你想至少要稍微有效一些,你應該檢測容器類型是否支持reserve()並在插入元素之前調用它。

下面的方法detects reserve() correctly(鏈接解釋如何):

template< typename C, typename = void > 
struct has_reserve 
    : std::false_type 
{}; 

template< typename C > 
struct has_reserve< C, std::enable_if_t< 
         std::is_same< 
          decltype(std::declval<C>().reserve(std::declval<typename C::size_type>())), 
          void 
         >::value 
         > > 
    : std::true_type 
{}; 

現在你可以用它來與std::enable_if_t有選擇地保留空間。一個例子可能是這樣的:

template< typename C > 
std::enable_if_t< !has_reserve<C>::value > 
optional_reserve(C&, std::size_t) {} 

template< typename C > 
std::enable_if_t< has_reserve<C>::value > 
optional_reserve(C& c, std::size_t n) 
{ 
    c.reserve(c.size() + n); 
} 

template< typename C, typename T, std::size_t N > 
void add_array(C& c, const std::array< T, N >& a) 
{ 
    optional_reserve(c, N); 
    for(const auto& e : a) { 
    c.insert(c.end(), typename C::value_type(e)); // see remark below 
    } 
} 

add_array現在可以與所有標準集裝箱(除std::forward_list)調用,它會調用reserve()std::vector和無序關聯容器。

由於上述不需要明確的專門化或重載特定容器類型,它也適用於非標準容器,只要它們的接口與標準容器的接口合理相似。 (事實上​​,我過去有幾個這樣的「自制」容器和上面的Just-Works™)

關於上述代碼轉換的評論:將T s轉換爲C::value_type的原因是表明如果需要的話,這將是正確的地方。在上面的例子中,它可能看起來多餘,但在我的真實世界的代碼中,我調用一個特殊的轉換特徵類來將e(這是編碼的字符串)轉換爲任何容器的正確值類型。

+1

適用於不那麼標準字符串容器。 :)我懷疑它不會比push_back()的體面編譯器和庫效率低 – sheddenizen

+0

如果我保留一個迭代器作爲狀態信息,正如我在上面的示例代碼中所做的那樣,這將與' push_back',不是嗎? – thirtythreeforty

+0

啊,除了當我使用'vector'時,通過重新分配:/使迭代器無效。那很危險! – thirtythreeforty

4

大多數情況下,人們使用特質。

許多提升庫已經解決了這個問題,所以你可能能夠重用現有的特徵。

一個簡單的演示:Live on Coliru

#include <vector> 
#include <set> 
#include <string> 

namespace traits 
{ 
    template <typename Container, typename Enable = void> 
     struct add_at_end; 

    template <typename... TAs> 
     struct add_at_end<std::vector<TAs...> > 
     { 
      using Container = std::vector<TAs...>; 

      template <typename... CtorArgs> 
      static void apply(Container& container, CtorArgs&&... args) { 
       container.emplace_back(std::forward<CtorArgs>(args)...); 
      } 
     }; 

    template <typename... TAs> 
     struct add_at_end<std::set<TAs...> > 
     { 
      using Container = std::set<TAs...>; 

      template <typename... CtorArgs> 
      static void apply(Container& container, CtorArgs&&... args) { 
       container.insert(container.end(), { std::forward<CtorArgs>(args)...}); 
      } 
     }; 

    template <typename... TAs> 
     struct add_at_end<std::basic_string<TAs...> > 
     { 
      using Container = std::basic_string<TAs...>; 

      template <typename... CtorArgs> 
      static void apply(Container& container, CtorArgs&&... args) { 
       container.insert(container.end(), { std::forward<CtorArgs>(args)...}); 
      } 
     }; 
} 

template <typename Container, typename... CtorArgs> 
    void add_to(Container& container, CtorArgs&&... args) { 
     traits::add_at_end<Container>::apply(container, std::forward<CtorArgs>(args)...); 
    } 

int main() 
{ 
    using X = std::pair<int, std::string>; 

    std::vector<X> v; 
    std::set<X> s; 
    std::wstring wstr; 
    std::string str; 

    add_to(v, 12, "hello"); 
    add_to(s, 42, "world"); 
    add_to(wstr, L'!'); 
    add_to(str, '?'); 
} 

基本上,你做了什麼,是有一個獨立的效用函數add_to使用一個特質類traits::add_at_end可以專門(在這種情況下,對於任何vector<...>set<...>basic_string<...>模板實例

實際上,您將分享類似容器的實現(例如dequevector)通過繼承常見的實現。

+0

我不明白代碼的作用,儘管我想我理解特質的概念。你能解釋一下代碼嗎?我在找什麼樣的特質? – thirtythreeforty

+0

查看擴展的答案並附上一些解釋(現在也顯示了字符串類型的專業化)。另見直播:http://coliru.stacked-crooked.com/a/b560a19849ac3057 – sehe

+0

有趣。 (我也看到'using'對於C++來說是一種'#DEFINE';不知道這一點!)我將研究這個;它可能最終是必要的。 STL是否提供了設置這個的框架? – thirtythreeforty

2

push_backstd::string,std::vectorstd::list支持。有了這個,你的類模板就是:

template<typename T> 
class S { 
    T built; 
public: 
    S() : built{} {} 
    void add_to(typename T::value_type e) { 
    built.push_back(e); 
    } 
    const T& get() { 
    return built; 
    } 
};