你使用哪些C++标准库的包装函数?

83

这个问题是今天早上问的,它让我想知道你认为哪些功能缺失于C++标准库,以及你如何使用包装函数填补这些空白。例如,我的工具库中有一个用于向向量附加的函数:

template <class T>
std::vector<T> & operator += ( std::vector<T> & v1,
                               const std::vector <T> & v2 ) {
    v1.insert( v1.end(), v2.begin(), v2.end() );
    return v1;
}

对于清除(或多或少)任何类型的函数 - 特别是在处理std::stack等内容时非常有用:

template <class C>
void Clear( C & c ) {
    c = C();
}

我还有一些,但我想知道你使用哪些?请将答案限制在包装器函数上——即不超过几行代码。


11
我包装了大部分 STL 算法,使其作用于整个容器而不是范围,这样做算数吗?因为操纵迭代器是一个很常见的错误 :)。 - Matthieu M.
4
因为vector::insert使用的是随机访问迭代器,所以一个好的实现会利用编译时分派来完成它自己的操作。 - Roger Pate
2
你最有用的C/C++代码片段是什么? - Roger Pate
4
c.swap(C()) 来清空容器不是更好吗? - Alexandre C.
5
@Alexandre:这是不允许的:它将一个临时变量绑定到了一个非const引用上。C().swap(c)可以工作。 - Roger Pate
显示剩余9条评论
26个回答

39

很多时候,我会把向量视为一组无特定顺序的项目(当然,当我不需要快速地检查元素是否在集合中时)。在这些情况下,调用erase()是浪费时间的,因为它会重新排序元素,而我不关心顺序。这就是下面这个O(1)函数派上用场 - 只需将最后一个元素移动到要删除的位置:

template<typename T>
void erase_unordered(std::vector<T>& v, size_t index)
{
    v[index] = v.back();
    v.pop_back();
}

13
在C++0x中,v[index] = st::move(v.back()); v.pop_back();是效率最高的方法之一。 - GManNickG
1
@Viktor:移动语义并不意味着“我拿走你的资源就这样了”,它们意味着“我拿走你的资源并使你处于无资源状态。”换句话说,您的移动语义需要确保对象可以安全地析构;这很容易做到,只需将指针设置为null即可。 - GManNickG
@Viktor:是的,我也这么认为。我甚至在这个答案之前就提出了一个问题 ,因此我对我的评论很有信心。一开始可能有点吓人,但如果你记住移动的对象必须有效,那就没问题了。 :) - GManNickG
@GMan:我希望编译器能够优化析构函数的调用,即Class x; Class y = std::move(x);意味着当超出作用域时,x的析构函数不会被调用。否则,我反对使它们有效=) - Viktor Sehr
移动语义并不能保证 O(1) 或比复制更好的性能。你认为你可以轻松地移动 std::array 吗? - v.oddou
显示剩余5条评论

38

boost::array

contains(container, val)(非常简单,但很方便)。

template<typename C, typename T>
bool contains(const C& container, const T& val) {
   return std::find(std::begin(container), std::end(container), val) != std::end(container);
}

remove_unstable(begin, end, value)

这是 std::remove 的一个更快的版本,但它不保留剩余对象的顺序。

template <typename T> 
T remove_unstable(T start, T stop, const typename T::value_type& val){  
    while(start != stop) {      
        if (*start == val) {            
            --stop;             
            ::std::iter_swap(start, stop);      
        } else {            
            ++start;        
        }   
    }   
    return stop; 
}

如果是pod类型(int、float等)的向量,并且几乎所有对象都被删除,使用std::remove可能更快。


4
有人认为contains需要一个第三个参数(bool sorted=false),并在sorted==true时特化调用binary_search而不是find,您同意吗? - KitsuneYMG
7
当您知道容器已排序时,只需直接调用binary_search即可。 - Roger Pate
2
在大多数最新的编译器中(包括Codewarrior),boost::array STL 的等效物可以在 tr1 命名空间中找到:std::tr1::array<>。 - Klaim

28
template < class T >
class temp_value {
    public :
        temp_value(T& var) : _var(var), _original(var) {}
        ~temp_value()        { _var = _original; }
    private :
        T&  _var;
        T   _original;
        temp_value(const temp_value&);
        temp_value& operator=(const temp_value&);
};

好的,由于看起来这不像我想象的那样简单,这里有一个解释:
在它的构造函数中,temp_value 存储了一个变量的引用和变量原始值的副本。在它的析构函数中,它将引用的变量恢复到其原始值。因此,在构建和销毁之间对变量进行的任何操作都会在 temp_value 对象超出作用域时被重置。
像这样使用它:

void f(some_type& var)
{
  temp_value<some_type> restorer(var); // remembers var's value

  // change var as you like
  g(var);

  // upon destruction restorer will restore var to its original value
}

这里有另一种使用作用域守卫技巧的方法:
namespace detail
{
    // use scope-guard trick
    class restorer_base
    {
    public:
        // call to flag the value shouldn't
        // be restored at destruction
        void dismiss(void) const
        {
            mDismissed = true;
        }

    protected:
        // creation
        restorer_base(void) :
        mDismissed(false) 
        {}

        restorer_base(const restorer_base& pOther) :
        mDismissed(pOther.is_dismissed())
        {
            // take "ownership"
            pOther.dismiss();
        }

        ~restorer_base(void) {} // non-virtual

        // query
        bool is_dismissed(void) const
        {
            return mDismissed;
        }

    private:
        // not copy-assignable, copy-constructibility is ok
        restorer_base& operator=(const restorer_base&);

        mutable bool mDismissed;
    };

    // generic single-value restorer, could be made 
    // variadic to store and restore several variables
    template <typename T>
    class restorer_holder : public restorer_base
    {
    public:
        restorer_holder(T& pX) :
        mX(pX),
        mValue(pX)
        {}

        ~restorer_holder(void)
        {
            if (!is_dismissed())
                mX = mValue;
        }

    private:
        // not copy-assignable, copy-constructibility is ok
        restorer_holder& operator=(const restorer_holder&);

        T& mX;
        T mValue;
    };
}

// store references to generated holders
typedef const detail::restorer_base& restorer;

// generator (could also be made variadic)
template <typename T>
detail::restorer_holder<T> store(T& pX)
{
    return detail::restorer_holder<T>(pX);
}

这只是一些样板代码,但可以更清晰地使用:

#include <iostream>

template <typename T>
void print(const T& pX)
{
    std::cout << pX << std::endl;
}

void foo(void)
{
    double d = 10.0;
    double e = 12.0;
    print(d); print(e);

    {
        restorer f = store(d);
        restorer g = store(e);

        d = -5.0;
        e = 3.1337;
        print(d); print(e);

        g.dismiss();
    }

    print(d); print(e);
}

int main(void)
{
    foo();

    int i = 5;
    print(i);

    {
        restorer r = store(i);

        i *= 123;
        print(i);
    }

    print(i);
}

它会移除其在类中的使用能力。
以下是第三种方法,可以达到相同的效果(不会出现可能抛出析构函数的问题):
实现:
//none -- it is built into the language

使用方法:

#include <iostream>

template <typename T>
void print(const T& pX)
{
    std::cout << pX << std::endl;
}

void foo(void)
{
    double d = 10.0;
    double e = 12.0;
    print(d); print(e);

    {
        double f(d);
        double g(e);

        f = -5.0;
        g = 3.1337;
        print(f); print(g);

        e = std::move(g);
    }

    print(d); print(e);
}

int main(void)
{
    foo();

    int i = 5;
    print(i);

    {
        int r(i);

        r *= 123;
        print(r);
    }

    print(i);
}

1
@Billy:它是为了以后自动恢复该值。 (并且val应在构造函数中被消除。) - Roger Pate
1
@dreamlax:我在答案中添加了一些描述性文本。现在是否可以理解,还是我需要更深入地介绍一下? - sbi
@GMan:我认为这就是 CW 的用途吧? - sbi
1
哦,第二个很聪明。 - jalf
1
这个有什么实际应用场景呢? - paulm
显示剩余2条评论

22

并不是一个封装器,但是臭名昭著的缺失了copy_if。取自这里

template<typename In, typename Out, typename Pred>
Out copy_if(In first, In last, Out res, Pred Pr)
{
    while (first != last) {
        if (Pr(*first)) {
            *res++ = *first;
        }
        ++first;
    }
    return res;
}

2
不回答问题,也不是标准库的包装器。 - Roger Pate
10
@Roger Pate,是的我知道,这就是为什么答案开头有“并非真正的包装器,但是......”这些词的原因。 - Glen
1
@Roger 的实现细节。如果你真的想的话,你可以用 remove_copy_if() 实现它。 :p - wilhelmtell

18
template< typename T, std::size_t sz >
inline T* begin(T (&array)[sz]) {return array;}

template< typename T, std::size_t sz >
inline T* end  (T (&array)[sz]) {return array + sz;}

2
我也有这些。:) 另外,你只需要两个(放弃 const 版本)。当数组是 const 时,T 将是 const U,你会得到预期的函数。 - GManNickG
1
@Marcus:这些比Boost.Range要老得多。:) - sbi
4
@Roger:它用于封装数组,以便它们可以与标准库一起使用。就是这样。:) - sbi
数组参数是按值传递的吗(导致复制)? - StackedCrooked
2
@Stacked, @sbe:无论是否有&,数组永远不会按值传递。&的存在是为了使数组长度的类型推断成立。 - Mankarse
显示剩余4条评论

12
有时候我觉得我在begin()end()之间陷入了困境。我想要一些类似于以下的函数:
template<typename T>
void sort(T& x)
{
    std::sort(x.begin(), x.end());
}

对于std::findstd::for_each以及所有STL算法的其他类似情况,也是如此。

我认为sort(x)sort(x.begin(), x.end())更容易阅读/理解。


1
使用´sort(boost::begin(x), boost:end(x));´代替,你也可以对数组进行排序。提示: - Viktor Sehr
4
Boost.Range V2具有适配器,可以用于整个标准库。 - ildjarn

9

我现在很少使用这个,但它曾经是一个基本工具:

template<typename T>
std::string make_string(const T& data) {
    std::ostringstream stream;
    stream << data;
    return stream.str();
}

我会尽快补充更多内容,哈哈。


3
Hehe -- 是 boost::lexical_cast<t, t> 的一种简化方式。 - Billy ONeal
1
是的,如果您不想将boost引入项目中,那个很棒。 - Steve
11
@BillyONeal: 这就是我不再使用它的原因。 @Steve: 这就是我仍然使用它的原因。 - Jon Purdy
char*std::string 上调用它是不必要的昂贵操作。也许需要进行模板特化? - wilhelmtell
如果我没记错的话,boost::lexical_cast 有一堆这样的特化和错误检查。不过,要将奇数转换为字符串,这个方法也可以正常工作。 - Jon Purdy

9
我有一个头文件,将以下内容放入“util”命名空间中:
// does a string contain another string
inline bool contains(const std::string &s1, const std::string &s2) {
    return s1.find(s2) != std::string::npos;
}

// remove trailing whitespace
inline std::string &rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
    return s;
}

// remove leading whitespace
inline std::string &ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace))));
    return s;
}

// remove whitespace from both ends
inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}

// split a string based on a delimeter and return the result (you pass an existing vector for the results)
inline std::vector<std::string> &split(const std::string &s, char delim, std::vector<std::string> &elems) {
    std::stringstream ss(s);
    std::string item;
    while(std::getline(ss, item, delim)) {
        elems.push_back(item);
    }
    return elems;
}

// same as above, but returns a vector for you
inline std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    return split(s, delim, elems);
}

// does a string end with another string
inline bool endswith(const std::string &s, const std::string &ending) {
    return ending.length() <= s.length() && s.substr(s.length() - ending.length()) == ending;
}

// does a string begin with another string  
inline bool beginswith(const std::string &s, const std::string &start) {
    return s.compare(0, start.length(), start) == 0;
}

2
split()函数会吞掉在std::getline()中发生的任何错误,默默地返回一个过短的向量。 - sbi
在检索字符串之前,当然应该先检查结果的 size() - Evan Teran
我怎样知道结果应该有多少个字符串? - sbi
这取决于您的使用情况。如果您有一个格式良好的基于行的输入文件,那么这将非常有效。如果您从任何地方获取随机数据,则可能需要更强大的东西来报告std::getline是否遇到错误。虽然我不确定在操作std::stringstreamstd::getline会失败的方式有多少种,但是您考虑了哪些类型的错误?显然,流的bad/fail位可以被设置,但是由什么导致的?什么样的输入可能会导致它失败? - Evan Teran
2
@sbi:您的评论引起了我的兴趣,关于在stringstream/getline循环中可能出错的实际情况(除了字符串根本没有足够的标记可以获取)。我在这里提出了一个问题:https://dev59.com/JUzSa4cB1Zd3GeqPq_Am - Evan Teran
2
@Evan:我改正了。请看我的评论,网址为http://stackoverflow.com/2563542#2563542。抱歉。 - sbi

9

每个人工具箱中的实用函数当然是 copy_if。虽然不是一个包装器。

我经常使用的另一个辅助程序是 deleter,这是一个我与 std::for_each 一起使用的函数对象,用于删除容器中的所有指针。

[编辑] 在我的 "sth.h" 中搜索,我还发现了 vector<wstring> StringSplit(wstring const&, wchar_t);


+1 对于删除器函数对象。我的删除器函数对象在大多数容器中都能很好地工作,但是我一直在尝试让它与 std::map 中的键或值为指针的情况下也能正常工作。我尝试使用类型特征来解决这个问题,但由于时间限制,进展不大。你是否解决了这个问题,或者你认为这是一个问题? - Glen
2
@Matthieu M. 不幸的是,我们并非所有人都能使用boost。 - Glen
@Glen:所有这些问题都可以通过使用智能指针来解决,最好是(但不一定)来自boost库。一个重要的副作用是,指向动态分配对象的容器也突然变得异常安全了。 - sbi
@Glen在这里,参与那些规定除了STD、包括Boost或TR1之外不能使用其他库的项目。 - wheaties
7
对于那些不幸的遭受企业愚蠢之害的人:请为内部图书馆申请一个例外,并将 Boost 的有用部分导入到内部图书馆中。即使在政治方面,所有问题也可以通过增加一层间接性来解决。 - MSalters

8
臭名昭著的缺失的erase算法:
  template <
    class Container,
    class Value
    >
  void erase(Container& ioContainer, Value const& iValue)
  {
    ioContainer.erase(
      std::remove(ioContainer.begin(),
                  ioContainer.end(),
                  iValue),
       ioContainer.end());
  } // erase

  template <
    class Container,
    class Pred
    >
  void erase_if(Container& ioContainer, Pred iPred)
  {
    ioContainer.erase(
      std::remove_if(ioContainer.begin(),
                     ioContainer.end(),
                     iPred),
       ioContainer.end());
  } // erase_if

+1,正要发布与我完全相同的答案。虽然我将其命名为remove_erase(...)。 - Viktor Sehr
2
唯一的问题是它破坏了STL中预期的语义擦除-删除惯用法。您需要使用擦除-删除惯用法来处理任何具有删除语义的算法,而不仅仅是std::remove。例如,std::unique - Billy ONeal
我已经为大多数STL算法做了容器适配 :) 但这是我最常用的一个,通常如果我想要唯一性,我会首先使用set - Matthieu M.
@Matthieu M.:请记住,这样做会让那些经常使用STL的人们警觉起来,“警告:没有调用ERASE函数的REMOVE-LIKE算法!!”。实际上并没有什么问题,但如果我需要与许多程序员共享我的代码,我不会这样做。只是我的个人意见。 - Billy ONeal
1
@Billy:这就是为什么我称其为“erase”而不是“remove”的原因。除此之外,我无能为力,只能让他们查看代码。幸运的是,现代IDE可以通过单击轻松找到定义 :) - Matthieu M.
如果在调用“erase()”之后需要向容器添加元素,那么这可能会很昂贵。我更喜欢“remove-erase习语”,因为它更冗长:这可能很昂贵。 - wilhelmtell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接