2010-11-15 72 views
3

爲了方便使用,我想寫格式相似的sprintf剛剛返回的std :: string的功能,像這樣:寫C++函數爲FORMAT_STRING等格式的std :: string的sprintf的

std::string format_string(const char* format, ...) 

我可以使用vsnprintf有但有問題 - 我不知道臨時緩衝區應該多長時間。在微軟有功能_vscprintf可以做到這一點,但我認爲它不便攜?

一個選項是臨時緩衝區啓動一些已知的大小,然後增加它,如果看到它不夠用vsnprintf。有更好的方法嗎?謝謝


P.S.請不加提升地回答。我知道Boost,但我很好奇沒有實現它。

+0

爲了方便使用,請考慮使用諸如Boost.Format之類的東西。變量函數不是類型安全的,不能用於非POD類型。 Boost.Format非常易於使用,是類型安全的,可以安全地用於任何類型。 – 2010-11-15 06:56:47

+0

@詹姆斯,謝謝,但我正在尋找答案沒有提升 – zaharpopov 2010-11-15 07:10:33

+0

你看過Boost.Format的來源?它可能會給你一些提示(但也可能需要一些時間來理解)。 – 2010-11-15 07:43:35

回答

2

一個選項是臨時緩衝區啓動一些已知的大小,然後增加它,如果看到它不夠用vsnprintf。有更好的方法嗎?謝謝

您可以使用vasprintf(),但這不需要堆分配 - 它不可能平均更快。使用alloca可以避免堆。或者,您可以直接寫入返回的string:NRVO應該避免複製,並且從C++ 11開始,移動語義會將成本sans-NRVO限制爲少數指針交換。

#include <cstdio> 
#include <cstdarg> 
#include <alloca.h> 

#include <string> 
#include <iostream> 

std::string stringf(const char* format, ...) 
{ 
    va_list arg_list;               
    va_start(arg_list, format);             

    // SUSv2 version doesn't work for buf NULL/size 0, so try printing 
    // into a small buffer that avoids the double-rendering and alloca path too... 
    char short_buf[256];               
    const size_t needed = vsnprintf(short_buf, sizeof short_buf, 
            format, arg_list) + 1; 
    if (needed <= sizeof short_buf) 
     return short_buf; 

    // need more space... 

    // OPTION 1 
    std::string result(needed, ' '); 
    vsnprintf(result.data(), needed, format, arg_list); 
    return result; // RVO ensures this is cheap 
OR 
    // OPTION 2 
    char* p = static_cast<char*>(alloca(needed)); // on stack 
    vsnprintf(p, needed, format, arg_list); 
    return p; // text copied into returned string 
} 

int main()                  
{                    
    std::string s = stringf("test '%s', n %8.2f\n", "hello world", 3.14);  
    std::cout << s;                
} 

一個簡單,最初更快的辦法是:

std::string result(255, ' '); // 255 spaces + NUL 
    const size_t needed = vsnprintf(result.data(), result.size() + 1, 
            format, arg_list); 
    result.resize(needed); // may truncate, leave or extend... 
    if (needed > 255) // needed doesn't count NUL 
     vsnprintf(result.data(), needed + 1, format, arg_list); 
    return result; 

潛在的問題是,你至少分配256個字符的短但是存儲的實際文本:可能加起來花費你在內存/緩存相關的性能。您可能能夠解決使用[shrink_to_fit] http://en.cppreference.com/w/cpp/string/basic_string/shrink_to_fit)的問題,但該標準並不要求它實際上做任何事情(的要求是「非綁定」)。如果最終不得不復制到一個新的精確大小的字符串,那麼最好使用本地字符數組。

+0

我想也可以代替alloca使用新的? alloca可移植到Windows? – zaharpopov 2010-11-15 12:19:24

+0

@zaharpopov:是的,如果你願意的話,你可以使用新的 - 如果處理任意數量的用戶提供的輸入(參見Linux上的man分配BUG),它可移植性更強,在某些系統上更安全,儘管它也比較慢自己刪除)。但是如果你正在考慮新的/刪除,你最好還是使用vasprintf(),因爲它將內存分配和打印結合起來,並且可能會有一些有用的優化(例如在耗盡時重新分配) - 你需要使用free()來釋放記憶雖然。 alloca在windows中可用...... VS2005中的一個快速檢查通過'#include '來撿起它。 – 2010-11-16 01:05:48

+0

'vsnprintf()'[documentation](http://www.cplusplus.com/reference/cstdio/vsnprintf/)表示如果發生格式化錯誤,則返回值爲'-1'。真的應該有一個檢查,以確保事實並非如此。 – andrewtc 2013-03-31 02:32:09

3

C99引入snprintf並且可能還有vsnprintf。有幾種便攜式開源實現(v)snprintf,如this one。後者也實現了動態分配存儲的vasprintf

也考慮C++ Format library它提供了一個類似於Boost格式的安全printf實現,但速度更快。

+0

+1,但是'boost :: format'特別是 – SingleNegationElimination 2010-11-15 07:17:45

+0

我在我的回答中提到vsnprintf - 問題是我不知道臨時緩衝區有多大可以使用 – zaharpopov 2010-11-15 07:25:26

+0

+1,但只適用於boost :: format,snprintf沒有任何std :: string – 2010-11-15 07:27:23

0

如果你不小心使用非標準功能(即:使用不同的功能,任何平臺,就像我得到了你從你的問題做),並希望快速的工作,你會發現asprintfvasprintf在GNU擴展中(就是這樣:也不是C或POSIX,而是由GCC和glibc支持)。

他們就像printfvsprintf,而是採取分配緩衝存儲器減輕你的工作照顧。

int asprintf(char **strp, const char *fmt, ...); 
int vasprintf(char **strp, const char *fmt, va_list ap); 

您可能會在任何系統上找到類似的功能。對於其他人,你可以寫一些代碼來分配一些緩衝區並將其傳遞給snprintf

+0

TX,但我尋找便攜式解決方案 – zaharpopov 2010-11-16 05:04:32

0

這樣做的強大方法是自己編寫整個函數。也就是說,不要轉發到其他類似printf的函數,而是自己分析並打印所有參數。掃描整個格式字符串,然後檢查參數以確定必要的緩衝區大小。隨後,打印到該緩衝區

這不是一個全有或無的建議;您仍然可以使用sprintf選定的類型。例如。當輸入格式字符串包含%6.4f參數時,使用sprintf(buf, "%6.4f", dbltemp);可能更容易,但您自己(簡單的memcpy)更好地處理了%s