擺脫printf的噩夢

衆所周知,printf是一個方便、直觀、易寫、變長參數的打印函數,但它有一個致命的缺陷,以下的語句將致使程序出現嚴重的運行時錯誤:html

printf("%s", 1);

而後程序中斷,程序員深夜被電話叫醒……噩夢被打斷之後陷入另外一個噩夢。深究之後發現,幕後黑手是C語言變長參數的傳遞問題。git

  1. 沒有傳遞參數類型。程序員

  2. 沒有傳遞參數個數。github

固然,在C語言裏邊仍是能夠解決這個問題滴,但……卻無可避免地失去了printf的優雅。C++改善了嗎?失望……編程

直到C++終於釋出了新的變元參數傳遞的標準。由Kenny Kerr(1)和sasha(2) 提出了設想。在github中,找到了一個解決方案 tinyformat(3)。json

但事情尚未結束,要把tinyformat用於log4cpp中,爲使用vc2010編程的項目提供一個的安全可靠的日誌模塊。尤爲是對於服務程序來講,日用不能停。安全

封裝log4cpp

log4cpp有一大堆配置語句,使用起來不是很方便,參照Lan'Sir(4)的文章進行以下封裝:多線程

class LogBase
{ 
private:
    static LogBase *m_pInstance;
    log4cpp::Category *m_pRoot;
protected: 
    LogBase() 
    {
        m_pRoot = &log4cpp::Category::getRoot();
        m_pRoot->setPriority(log4cpp::Priority::INFO);
    } 
private:
    LogBase& operator=(LogBase&); 
    LogBase(const LogBase&); 

public: 
    ~LogBase()
    { 
        if ( m_pRoot != NULL) { delete m_pRoot; m_pRoot = NULL; } 
        if ( m_pInstance != NULL) { delete m_pInstance; m_pInstance = NULL; }
    }
    static LogBase* getInstance()
    {  
        if( m_pInstance == NULL) m_pInstance = new LogBase();

        return m_pInstance;
    }
    void log(log4cpp::Priority::Value priority, const std::string& message)
    {
        if (m_pRoot != NULL) m_pRoot->log(priority, message);
    }
};

LogBase* LogBase::m_pInstance = NULL;

使用tinyformat

使人鬱悶的是vs2010支持C++98而不是C++11。幸虧tinyformat做者已經作了一些工做——用宏定義循環設置了1-16個變元的函數。函數

// C++98 version
void log_info(const char* s)
{
    std::string m = s;
    LogBase *pInstance = LogBase::getInstance();  \
    if (pInstance != NULL) pInstance->log(log4cpp::Priority::INFO, s);  \
}
#define MAKE_LOG_INFO_FUNC(n)   \
template<TINYFORMAT_ARGTYPES(n)>    \
void log_info(const char* fmt, TINYFORMAT_VARARGS(n))   \
{   \
    std::string m = tfm::format(fmt, TINYFORMAT_PASSARGS(n));   \
    LogBase *pInstance = LogBase::getInstance();  \
    if (pInstance != NULL) pInstance->log(log4cpp::Priority::INFO, m);  \
}
TINYFORMAT_FOREACH_ARGNUM(MAKE_LOG_INFO_FUNC)

如今就能夠試一試了:.net

int _tmain(int argc, _TCHAR* argv[])
{
    log_info("hello %s!", 555);
    log_info("hello world%s!");
    return 0;

}

Tips

  1. 在本身的項目中同時使用jsoncpp和log4cpp的lib

    分別編譯lib時,需將屬性.C/C++.代碼生成.運行庫統一。例如都設置爲「多線程/MT」或者都設置爲「多線程調試/MTD」。

    在不一樣類型的運行庫中存在一樣的一些函數名,鏈接在一塊兒時將出現重複定義和定義衝突錯誤。

    具體內容可參考文章:「關於VC link2005 重複定義,定義衝突的錯誤解決」(5)以及「配置本身的OpenGL庫,glew、freeglut庫編譯,庫衝突解決(附OpenGL Demo程序)」(6)

  2. 消除警告:未找到 PDB「vc100.pdb」

    編譯jsoncpp和log4cpp的lib時,部分調試信息被放在 vc100.pdb 文件中。編譯連接本身的項目時出現找不到 vc100.pdb 的告警信息。

    通常狀況下,在調試本身的項目時都不會跟蹤到jsoncpp和log4cpp的內部。若是真的懷疑它們有問題,也是到它們各自的項目中進行調試。所以,簡單合理的解決辦法是在編譯lib時,關閉調試信息。

    屬性.C/C++.常規.調試信息格式設置爲空。

    若是在本身的項目中仍然但願保留lib的這些調試信息,要解決文件名衝突的問題。詳見「warning LNK4099: 未找到 PDB」(7)

  3. 消除警告:沒法解析的外部符號 __imp__WSACleanup

    編譯log4cpp的lib時可能會用到ws2_32.lib。詳見「沒法解析的外部符號 __imp__WSACleanup@」(8)。

    在添加頭文件處,添加引用:

#pragma comment(lib,"ws2_32.lib")

參考連接:

  1. Windows with C++ - Using Printf with Modern C++

  2. Compile-Time and Runtime-Safe Replacement for 「printf」

  3. https://github.com/c42f/tinyformat

  4. Linux混合編程+log4cpp

  5. 關於VC link2005 重複定義,定義衝突的錯誤解決

  6. 配置本身的OpenGL庫,glew、freeglut庫編譯,庫衝突解決(附OpenGL Demo程序)

  7. warning LNK4099: 未找到 PDB「vc120.pdb」

  8. 沒法解析的外部符號 __imp__WSACleanup@

相關文章
相關標籤/搜索