类型安全且自动管理内存的返回 std::string 的 sprintf 实现

这篇博文里,我提到了一个例子,说的是使用C++实现类型安全的printf。这个例子很惊艳,但是在我写程序的时候,并非那么“迫切”地需要它出现在我的工具箱中,因为它并不比普通的printf方便,而且它没有出现的标准库中。所以自己也懒得整。相反,这个函数的兄弟,sprintf,倒是一个非常需要的函数。不仅仅是因为需要它类型安全,而是 sprintf 有比 printf 更多的麻烦:

  1. 首先它确实也不是类型安全的
  2. 使用sprintf之前,必须要先准备一段buffer,但这个buffer的大小难以确定,还要防溢出
  3. C++程序中多数时候我还是需要使用 string,于是还是要再用buffer中的字符串再生成一个string对象来使用。

这非常的麻烦,但是,sprintf 紧凑的表达是它最大的优点~我才不会用流去格式化一个字符串呢,实在是让人精神分裂啊。

想到用 variadic template 实现的 printf 之后,觉得应该是完全可以模仿这个做一个 sprintf 函数的啊,除了得到类型安全的好外之外,还能直接从函数返回字符串,又不会有溢出的问题,string和流自动地把内存管理好。一举多得。

std::string sprintf(const char *s)
{
    std::stringstream ss;
    _sprintf(ss, s);
    return ss.str();
}

void _sprintf(std::stringstream & ss, const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        ss << *s++;
    }
}

template<typename T,typename... Args>
std::string sprintf(const char *s, T value, Args... args) {
    std::stringstream ss;
    _sprintf(ss, s, value, args...);
    return ss.str();
}

template<typename T, typename... Args>
void _sprintf(std::stringstream & ss, const char *s, T value, Args... args)
{
    
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                ss << value;
                _sprintf(ss, s + 1, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        ss << *s++;
    }
    throw std::logic_error("extra arguments provided to lyw::sprintf");
}

使用起来也很方便,直接像原来的sprintf一样用就好了。而且如果自定义的对象实现的流操作符重载,就可以自动地与这个函数配合。非常精巧。

string str = sprintf("I tried % times in % ", 10, "Monday");

只需要加一点点代码(使用C++的流控制符),就可以实现各加精细的格式,这个版本我就不再贴了。

PS: 代码在VS2013上跑,很High,

      VS2013在C++的开发环境(我是指编辑器)上做了很大的改进,而且速度也快了。算是难得

 

posted on 2013-11-05 23:41  唐风思琪  阅读(2794)  评论(0编辑  收藏  举报

导航