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






3.20/5 (5投票s)
使用 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`算法,因此库的供应商可以自由选择任何方法。
参考
- [ISO98] 《C++国际标准编程语言》 ISO/ICE 14882 **1998**
- [JOS99] 《C++标准库:教程和参考》。Nicolai M. Josuttis **1999**