结合样板载体<> C ++中的代码

时间:2014-10-19 07:43:31

标签: c++ c++11 stl

我在操作vector<T>时使用的某些功能很多,但标准界面很笨重。

例如,假设v的{​​{1}}类型为vector<T>。理想情况下,我想拨打电话:

typename T

等等。

这些都可以在stl中完成,但界面笨重且冗长。例如, v.probe(x) //returns true if x is in v v.sort() // sort v v.unique() // unique elements of x v.locate(x) // pointer to the element in v equal to x if it exists, otherwise NULL v.cat(w) // concatenate vector w to x v.erase(x) // erase all x’s from v 就像

v.probe(x)

std::find(v.begin(),v.end(),x)!=v.end()

v.sort

这使得std::sort(v.begin(),v.end()) 在复杂左值表达式的情况下非常难以使用,需要临时的。 (即,我不能轻易地对std::sort进行排序。

在STL中获得v的独特价值更加笨拙,我相信:

foo->bar.names[3]

我认为几乎每个C ++程序员都遇到过这个问题或类似问题。

最好的方法是什么?

我考虑了3个选项:

为我在代码中使用的每种std::erase(std::unique(std::sort(v.begin(),v.end()).end(),v.end()) 类型编写特殊用途代码。

为常见的矢量函数编写模板头

拥有个人vector<>vector<T>,将K<T>和mixin班vector<T>与我需要的算法同步。

选项1似乎很简单,但一段时间后变得非常罗嗦。 选项2并不像看起来那么简单。考虑编写一个特殊的函数探针,比如

algorithm_vector<T>

嗯,事实是,如果 template<typename T> probe(const vector<T> & v, const T &x).... 的大小很大,我们实际上只想通过引用传递x,否则我们想要使用值。我甚至不知道如何编写一个模板函数,智能地决定是否通过值或引用传递其参数,即使我这样做,也很难做到。

选项3可能是最干净的,但有语义问题使其不清楚。

总之,我的问题是:在向量上添加常见的,简单的通用函数到程序的最佳方法是什么?

(另外,作为可能对此有所了解的一个可选点,我不明白为什么STL会像搜索元素的向量或对矢量进行排序等常见的事情那么冗长和笨拙。是否存在STL使最常见的用法变得如此冗长,并且在整个容器上不会超载默认值的一些原因?)

5 个答案:

答案 0 :(得分:5)

我不会使用任何方法并选择使用标准算法。它们是众所周知的,任何阅读代码的程序员都会理解你要做的事情。:)

例如功能

template<typename T> probe(const vector<T> & v, const T &x)....

只会让读者感到困惑。当我看到标准算法std::find时,我不需要滚动代码来查找函数的定义。当我看到函数探测器时,我需要滚动你的代码来找到函数定义并理解函数的作用。 :)

答案 1 :(得分:3)

STL背后的重要思想是容器,迭代器和算法三个方面。重要的观察结果是,大多数算法仅在它们所需的迭代器类型上有所不同,即它们对底层容器(如果有的话)是无知的。这带来了很大的灵活性,但实际上,它创建了一个稍微笨重的界面。

一种稍微更现代的方法来自于迭代器通常成对出现的观察结果。结合两个迭代器,你得到一个范围。查看基于此观察结果的Boost's Range library。使用这个库或者只是它背后的想法应该为你提供保持灵活性的方法,同时提供一个不那么详细的语法。

答案 2 :(得分:1)

我会定义一些尽可能通用的简单辅助函数,例如

#include <algorithm>
#include <vector>
#include <iterator>
#include <stddef.h>         // ptrdiff_t

#define CPPX_ITEMS_OF( c )  std::begin( c ), std::end( c )

namespace cppx {
    using std::begin;  using std::end;
    using std::ostream;

    using Size = ptrdiff_t;

    template< class Container >
    auto n_items( Container const& c )
        -> Size
    { return end( c ) - begin( c ); }

    template< class Value, class Container >
    auto contains( Value&& value, Container&& container )
        -> bool
    { return (find( CPPX_ITEMS_OF( container ), value ) != container.end()); }

    template< class Container >
    void sort( Container&& c ) { sort( CPPX_ITEMS_OF( c ) ); }

    template< class Container, class EnableIf_ = typename Container::value_type >
    auto operator<<( ostream& stream, Container const& c )
        -> ostream&
    {
        stream << "{";
        bool first = true;
        for( auto const& value : c )
        {
            if( !first ) { stream << ", "; }
            stream << value;
            first = false;
        }
        stream << "}";
        return stream;
    }

    template< class Container >
    auto uniqued( Container&& c )
        -> decltype( begin( c ) )
    { return unique( CPPX_ITEMS_OF( c ) ); }

    template< class It, class Container >
    void erase_from( It const it, Container&& c ) { c.erase( it, c.end() ); }

    template< class Container >
    void shorten_to_unique( Container&& c ) { erase_from( uniqued( c ), c ); }

    template< class Value, class Container >
    auto find( Value const& v, Container const& c )
        -> decltype( begin( c ) )
    { return find( CPPX_ITEMS_OF( c ), v ); }

    template< class Dest_container, class Source_container >
    void append_to( Dest_container& dest, Source_container const& src )
    {
        dest.reserve( dest.size() + n_items( src ) );
        for( auto const& v : src ) { dest.push_back( v ); }
    }

    template< class Value, class Container >
    void remove_all( Value const& v, Container&& c )
    {
        c.erase( remove( CPPX_ITEMS_OF( c ), v ), c.end() );
    }
}    // namespace cppx

与使用这些功能相比,直接使用标准库:

#include <iostream>
using namespace std;
using cppx::operator<<;

#define T( e ) (cout << #e << "  >>>  ", e)

void use_std()
{
    vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};

    cout << T( find( v.begin(), v.end(), 3 ) != v.end() ) << endl;
    cout << T( find( v.begin(), v.end(), 7 ) != v.end() ) << endl;

    T( sort( v.begin(), v.end() ) );  cout << v << endl;
    T( v.erase( unique( v.begin(), v.end() ), v.end() ) );  cout << v << endl;
    cout << T( *find( v.begin(), v.end(), 9 ) ) << endl;
    T( ([&](){ vector<int> const x{ 5, 5, 5  }; copy( x.begin(), x.end(), back_inserter( v ) ); }()) );  cout << v << endl;
    T( v.erase( remove( v.begin(), v.end(), 5 ), v.end() ) );  cout << v << endl;
}

void use_cppx()
{
    using namespace cppx;
    vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};

    cout << T( contains( 3, v ) ) << endl;
    cout << T( contains( 7, v ) ) << endl;

    T( sort( v ) );  cout << v << endl;
    T( shorten_to_unique( v ) );  cout << v << endl;
    cout << T( *find( 9, v ) ) << endl;
    T( append_to( v, vector<int>{ 5, 5, 5  } ) );  cout << v << endl;
    T( remove_all( 5, v ) );  cout << v << endl;
}

auto main() -> int
{
    cout << boolalpha;

    cout << "    Using standard library:" << endl;
    use_std();
    cout << "\n* * *\n" << endl;
    cout << "    Using wrappers:" << endl;
    use_cppx();
}

输出:

    Using standard library:
find( v.begin(), v.end(), 3 ) != v.end()  >>>  true
find( v.begin(), v.end(), 7 ) != v.end()  >>>  false
sort( v.begin(), v.end() )  >>>  {1, 1, 2, 3, 4, 4, 5, 5, 6, 9}
v.erase( unique( v.begin(), v.end() ), v.end() )  >>>  {1, 2, 3, 4, 5, 6, 9}
*find( v.begin(), v.end(), 9 )  >>>  9
([&](){ vector<int> const x{ 5, 5, 5 }; copy( x.begin(), x.end(), back_inserter( v ) ); }())  >>>  {1, 2, 3, 4, 5, 6, 9, 5, 5, 5}
v.erase( remove( v.begin(), v.end(), 5 ), v.end() )  >>>  {1, 2, 3, 4, 6, 9}

* * *

    Using wrappers:
contains( 3, v )  >>>  true
contains( 7, v )  >>>  false
sort( v )  >>>  {1, 1, 2, 3, 4, 4, 5, 5, 6, 9}
shorten_to_unique( v )  >>>  {1, 2, 3, 4, 5, 6, 9}
*find( 9, v )  >>>  9
append_to( v, vector<int>{ 5, 5, 5 } )  >>>  {1, 2, 3, 4, 5, 6, 9, 5, 5, 5}
remove_all( 5, v )  >>>  {1, 2, 3, 4, 6, 9}

正如我所看到的,直接标准库使用的复杂性和冗余性是支持精心设计的包装器的有力论据。

一个人不应该主动解密源代码,就像直接使用标准库一样。

答案 3 :(得分:0)

STL迭代器不知道何时传递所有对象。这是同时的优点和缺点。因为sintaksis(vector :: iterator i = v.begin(); i!= v.end(); ++ i)...的优点非常接近指针sintaksis并且编译器没有问题非常有效的机器代码,您可以在模板中使用指针作为迭代器。您在帖子中描述的缺点 - 您总是需要使用2个迭代器 - 用于任何带有集合的工作。

答案 4 :(得分:0)

在单独的命名空间中定义自己的Vector类。 不要从std :: vector派生,因为stl容器没有虚拟析构函数,你最终可能会遇到一些时髦的副作用。 而是在你的类中拥有一个std :: vector,并声明你可能觉得有用的所有方法:

一个简单的例子:

template <typename T>
struct Vector
{
 //returns true if x is in v
 bool probe(x) { std::find(v.begin(),v.end(),x)!=v.end() }
private:
 std::vector<T> v_;
}

定义ctor / dtor / copy构造函数等的杂事留给读者 请注意,这种方法违反了C ++的“良好实践”,通常我们将数据与算法分开。这是stl的基础。 我建议你这个有趣的读物,由Stepanov,他的父亲。 http://www.stepanovpapers.com/notes.pdf

主要是代码重用的问题,在stl中你可以拥有可以在任何stl容器或任何其他容器上使用的算法。它需要两个整数和一个比较函数。 在其他语言中,他们通过拥有一个基类来解决这个问题,其中每个其他容器都继承自(Java中的Object),并且在对象本身上有方法。