c++ stream manipulator 的实现机制

C++ stream manipulator,是 stream 的一种特殊的操作方式。manipulator 中文也翻译作运算子或者操作符。只要用到 stream,就必然会涉及到 manipulator。

比如下面的代码:

std::cout << "hello world!" << std::endl;

这里的 std::endl 就是一个最常用的 manipulator,类似的还有 std::hex,std::setw 等。那么他们的实现机理是怎样的呢?

 

首先看 std::endl, 它被定义为一个函数。参数就是 ostream& (即 cout 的类型),返回值也是 ostream&:

ostream& endl(ostream& obj); 

所以对于 ostream manipulator 可以这样定义:返回值和第一个参数都是 ostream 的函数.

下面的代码是一个较为完整的模拟 manipulator 的例子:

#include <iostream>

std::ostream& space(std::ostream& os)
{
    os << ' ';
    return os;
}

int main(int, char*[])
{
    std::cout << "[" << space << "]" << std::endl;
    return 0;
}

 

当然,目前我们模拟的manipulator 函数也有限制: 它不能带参数。

比如,我们想实现一个新的manipulator,命名为 spaces,它可以接受一个参数 int count,输出 count 个空格,使用起来是下面的样子:

 std::cout << "[" << spaces(5) << "]" << std::endl; 

则刚才的定义函数的思路,定义一个新的函数 spaces,让他除了 ostream&,还可以多接收一个 int 参数: 

std::ostream& spaces(std::ostream& os, int count)
{
    for (int i = 0; i < count; i++)
        os << '.';
    return os;
};

std::cout << "[" << spaces(5) << "]" << std::endl; // compile error

但调用 spaces 的程序没法写,写成上面的样子编译器会报错。

那调用的时候再如果加上一个参数呢? std::cout << "[" << spaces(std::cout, 5) << "]" << std::endl; 

这样也行不通。因为按照C++语法,编译器会先计算这个函数的返回值,则这行代码最终会生成类似

spaces(std::cout, 5);
std::cout << "[" << (void*)std::cout << "]" << std::endl;

的代码。运行结果有可能会是这个样子: .....[0x601088] 

 

所以,这里必须存一个函数地址,或者是看起来像函数的东西:它只能先存下参数,但不能马上调用。

那么是不是可以就要用到仿函数的概念,把 spaces 实现为一个 functor,这样在调用  << spaces(5) 的时候,先生成一个 spaces 对象,把参数保存好,但先不调用功能。

为了能够接收 ostream& 参数,我们还需要为 spaces 类型定义一个 ostream operator << 的重载: std::ostream& operator << (std::ostream& os, spaces f)  这样在真正需要调用功能的时候才执行:

#include <iostream>
struct spaces
{
int _count;
spaces(
int count) : _count(count) { } std::ostream& operator()(std::ostream& os)
{
for (int i = 0; i < _count; i++) os << ' '; return os; } }; std::ostream& operator << (std::ostream& os, spaces f)
{
return f(os); } int main(int, char*[])
{ std::cout
<< "[" << spaces(5) << "]" << std::endl; return 0; }

这样的实现已经可以达到我们的要求,运行结果可以正确输出: [ ] 

 

如果作为库的实现者,需要考虑的要更多:

如果为每一个 manipulator 都实现了一个 functor,这就需要为每一种 functor都实现一遍  std::ostream& operator << (std::ostream& os, functor f) ,但内部实现都一样。

如果 manipulator 特别多,则带来的弊端是大量的 manipulator 类定义,且每种 operator<< 的重载实现都看似重复。

 

vs2010 的 C++ 库实现是这样的:

定义一个通用的 functor 模板 _Smanip,通过它把参数和调用函数地址存起来,这里用 setbase 举例:

// ------------ iomanip --------------------
template<class _Arg>
struct _Smanip
{    // store function pointer and argument value
    _Smanip(void (__cdecl *_Left)(ios_base&, _Arg), _Arg _Val)
        : _Pfun(_Left), _Manarg(_Val)
        {    // construct from function pointer and argument value
        }

    void (__cdecl *_Pfun)(ios_base&, _Arg);    // the function pointer
    _Arg _Manarg;    // the argument value
};
    
_MRTIMP2 _Smanip<int> __cdecl setbase(int);

// ----------  iomanip.cpp ------------------
static void __cdecl sbfun(ios_base& iostr, int base)
{    // set base
    iostr.setf(base == 8 ? ios_base::oct
        : base == 10 ? ios_base::dec
        : base == 16 ? ios_base::hex
        : ios_base::_Fmtzero,
            ios_base::basefield);
}

_MRTIMP2 _Smanip<int> __cdecl setbase(int base)
{    // manipulator to set base
    return (_Smanip<int>(&sbfun, base));
}

代码不难理解:manipulator 的形式是 setbase(int),有一个实现其功能的函数 sbfun(ios_base&, int),通过 _Smanip<int> 将二者联系起来。

 

当然各家的实现方法都不一样:

我的 linux-mint 上带的是 gcc 4.8.2-19,里面的 glibc 没有用 functor,每种 manipulator 只定义一个结构保存参数,真正的功能实现在 operator << 的重载函数里。

struct _Setbase { int _M_base; };

inline _Setbase setbase(int __base)
{ 
    return { __base }; 
}

template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>& 
operator<<(basic_ostream<_CharT, _Traits>& __os, _Setbase __f)
{
    __os.setf(__f._M_base ==  8 ? ios_base::oct : 
        __f._M_base == 10 ? ios_base::dec : 
        __f._M_base == 16 ? ios_base::hex : 
        ios_base::fmtflags(0), ios_base::basefield);
    return __os; 
}

 

到目前为止,C++ stream manipulator 的实现机制就已经很清楚了。文中只涉及到了一个参数的 manipulator,没有参数或者两个或者多个参数的原理也是一样的。

基本思路就是:

1. 利用一个类 manip,把参数存起来(如果没有参数这一步就省了)。

2. 通过重载 operator << (ostream&, manip)  响应 << 的操作。

如果 manipulator 没有参数,第1步就退化为直接定义一个参数为 stream 对象引用的函数。

如果不想把实现写在 operator << 的重载函数里,而是单独提出来放在一个函数 mani_impl 里,则第1步还要将 mani_impl 的地址存在 manip 对象里,以便在 operator << 的重载函数里调用。

 

转载于:https://www.cnblogs.com/yangchaobj/archive/2013/02/12/2910204.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值