65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 STL 在 C++ 中编写可移植代码

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.20/5 (5投票s)

2003年12月31日

CPOL

4分钟阅读

viewsIcon

48382

使用 STL 在 C++ 中编写可移植代码

引言

C++标准[ISO98]并没有规定编写可移植代码的所有问题。一些问题留给了编译器和库的实现。所有这些问题都应该小心处理,以使你的代码具有可移植性,这使得编写可移植代码既具有挑战性,也充满乐趣。

一个例子是数据类型的尺寸。C++标准没有指定内置数据类型的尺寸,所以它依赖于特定平台的实现。因此,即使代码是根据语言标准编写的,并且在某些平台上编译和运行良好,任何基于数据类型大小编写的代码都不是可移植的。让我们来看一个小的例子来更好地理解这一点。

int array[4 - sizeof(int)];

这段代码显然不可移植,尽管它可能在某些平台上运行良好。如果整数的大小是2字节,那么这段代码将创建一个包含2个整数元素的数组;但是如果整数的大小是4字节,那么这段代码是未定义的。根据C++标准,“取消引用请求为零大小的指针的结果是未定义的”。

使用标准模板库也可能出现同样的情况。标准指定了算法的规范,但没有指定算法的实现细节,因此它在不同的平台上可能不同。这种情况可能出现在标准模板库[JOS99]中的`remove_if`算法中。

详细说明

让我们来看第一个关于`remove_if`算法使用的简单例子。这个算法需要两个前向迭代器和一个谓词。

template<class _FwdIt, class _Pr> inline
_FwdIt remove_if(_FwdIt _First, _FwdIt _Last, _Pr _Pred)

根据C++标准,“C++标准库中任何全局函数是否定义为内联是未指定的”。因此它在不同的平台上可能是内联的,也可能不是内联的,但这并不是我们目前的问题。

谓词可以是函数对象或函数指针。尽管不应该,但`remove_if`算法在函数对象和函数指针之间的行为略有不同。函数对象可以将状态存储在成员变量中,而函数指针则不能。对于函数指针谓词,函数可以通过使用静态变量来维护其状态,从而模拟函数对象的行为,但是你可以创建两个函数对象来存储两个不同的状态,而对于简单的函数则不可能。而这正是细微的差别,它可能在不同的实现中产生不同的行为。

这是一个例子,展示了如何使用函数指针作为谓词。谓词函数用于删除向量中的第三个元素。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

bool Remove3rd(int iVal)
{
    return 3 == iVal;
}

int main()
{
    vector<int> vec;

    for (int iIndex = 1; iIndex < 10; ++iIndex)
    {
        vec.push_back(iIndex);
    }

    vector<int>::iterator iter_ = remove_if(vec.begin(), 
             vec.end(), Remove3rd);

    vec.erase(iter_, vec.end());

    copy(vec.begin(), vec.end(), ostream_iterator<int>(cout, " "));

    return 0;
}

该程序的输出是

1 2 4 5 6 7 8 9

这正是我们需要的输出。现在尝试使用函数对象而不是函数指针作为谓词来编写相同的程序。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

template <typename T>
class Remove3rd
{
public:
    bool operator () (T iValue)
    {
        return 3 == iValue;
    }
};

int main()
{
    vector<int> vec;

    for (int iIndex = 1; iIndex < 10; ++iIndex)
    {
        vec.push_back(iIndex);
    }

    vector<int>::iterator iter_ = remove_if(vec.begin(), 
            vec.end(), Remove3rd<int>());

    vec.erase(iter_, vec.end());

    copy(vec.begin(), vec.end(), ostream_iterator<int>(cout, " "));

    return 0;
}

这个程序的输出也是相同的。现在让我们利用函数对象的优势,尝试使其更通用。目前我们只能删除向量中的第三个元素,但是借助一个成员变量,我们可以使其可配置。借助另一个成员变量,我们可以存储`<span class="cpp-keyword">operator</span>()`函数被调用的次数。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

template <typename T>
class RemoveNth
{
private:
    T m_tValue;
    T m_iCount;

public:
    RemoveNth(T p_tValue) : m_tValue(p_tValue), m_iCount(0) { }
    bool operator () (T)
    {
        return ++m_iCount == m_tValue;
    }
};

int main()
{
    vector<int> vec;

    for (int iIndex = 1; iIndex < 10; ++iIndex)
    {
        vec.push_back(iIndex);
    }

    vector<int>::iterator iter_ = remove_if(vec.begin(), 
          vec.end(), RemoveNth<int>(3));

    vec.erase(iter_, vec.end());

    copy(vec.begin(), vec.end(), ostream_iterator<int>>(cout, " "));

    return 0;
}

这个程序的输出是什么?这个程序的输出取决于`remove_if`算法的实现。你可能会得到以下输出

1 2 4 5 6 7 8 9

或者你可能会得到这个输出

1 2 4 5 7 8 9

`remove_if`的实现可能首先调用`find_if`算法来搜索容器中满足谓词的元素。它的实现可能类似于这样。

template<class _FwdIt, class _Pr> inline
_FwdIt remove_if(_FwdIt _First, _FwdIt _Last, _Pr _Pred)
{    
    // remove each satisfying _Pred
    _First = find_if(_First, _Last, _Pred);

    if (_First == _Last)
        return (_First);    // empty sequence, all done
    else
    {    
        // nonempty sequence, worth doing
        _FwdIt _First1 = _First;
        return (remove_copy_if(++_First1, _Last, _First, _Pred));
    }
}

而`find_if`算法的签名可能类似于这样

template<class _InIt, class _Pr> inline
_InIt find_if(_InIt _First, _InIt _Last, _Pr _Pred)

看看这个函数的最后一个参数,谓词是以值而不是以引用传递的,这正是标准所要求的。因此,它的(谓词的)副本被创建并传递到`find_if`算法中。所以这个谓词与传递到`remove_if`算法中的谓词不同。简而言之,一个谓词副本负责删除第三个元素,另一个谓词副本负责删除第六个元素。注意9没有从列表中删除,这意味着它不会删除所有位置是3的倍数的元素。

另一方面,如果`remove_if`算法的实现是这样的,那么它的行为就不同了。

template<class _FwdIt, class _Pr> inline
_FwdIt remove_if(_FwdIt _First, _FwdIt _Last, _Pr _Pred)
{    
    // remove each satisfying _Pred
    for (; _First != _Last; ++_First)
        if (_Pred(*_First))
            break;

    if (_First == _Last)
        return (_First);    // empty sequence, all done
    else
    {    
        // nonempty sequence, worth doing
        _FwdIt _First1 = _First;
        return (remove_copy_if(++_First1, _Last, _First, _Pred));
    }
}

那么程序的输出是这样的

1 2 4 5 6 7 8 9

这等于函数指针谓词。另一种可能的解决方案是在`find_if`算法中以引用传递谓词,但这并不是标准要求的。标准没有规定如何实现`remove_if`算法,因此库的供应商可以自由选择任何方法。

参考

  1. [ISO98] 《C++国际标准编程语言》 ISO/ICE 14882 **1998**
  2. [JOS99] 《C++标准库:教程和参考》。Nicolai M. Josuttis **1999**
© . All rights reserved.