2014-04-03 51 views
2

Python中的打印函數使用可自定義的分隔符自動分隔其參數。有沒有什麼方法可以通過使用流操縱器在C++中模擬這種行爲?如何實現自動添加分隔符的自定義粘性操作器?

也就是說,下面的C++代碼:

std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl; 

應工作類似於下面的Python代碼:

print(1, "two", 3, sep=", ") 

所需的輸出將是:

1, two, 3 

將如何我去執行custom::sep?這似乎比標準的自定義操縱器更棘手,因爲它不能只改變流中的下一個項目,如herehere。它應該粘到下一個custom::sepstd::endl。此外,它不能只處理數字或某些類型,如here。它應該適用於任何流式類型。

+0

流不支持您提出的設計。沒有勾選「輸出項目」。但爲什麼不編寫「打印」功能,而不是試圖將流打到非自然的事情?或者你可以支持一個字符串的'<<'符號作爲目的地,即字符串生成器。兩者都相當容易做到。 –

+2

@kloffy這是可能的,但不是直接在'std :: cout'內。我正在寫回復 – Raxvan

+0

感謝您的意見。我的問題的部分動機是找出是否有可能。我期待着看到你的解決方案@Raxvan! – kloffy

回答

2

您發佈的解決方案的問題在於它依賴於定製使用構面格式化整數的方式。不幸的是,我不認爲有相應的工具可以用於任意類型。

有。您可以使用流的底層緩衝區來獲取所需內容。緩衝區是字符序列最終收集起來進行維護的地方。以下代碼將創建一個流緩衝區,該緩衝區保存對您希望使用其字符序列的對象的引用。我們設置std::ios_base::unitbuf格式標誌,以便在每次輸出操作時刷新流(所以我們可以將分隔符添加到結尾)。

推而廣之,它也可以讓您卸載該分離器,並確保沒有內存的過程中泄露:

#include <iostream> 

namespace custom 
{ 
    struct sep_impl 
    { 
     sep_impl(std::string const& separator); 
     std::string separator; 
    }; 

    sep_impl sep(std::string const& str) 
    { 
     return sep_impl(str); 
    } 

    std::ostream& nosep(std::ostream& os); 
} 

int separatorEnabled() 
         { static int idx = std::ios_base::xalloc(); return idx; } 
int getSeparator() { static int idx = std::ios_base::xalloc(); return idx; } 

struct custom_separator : std::streambuf 
{ 
public: 
    custom_separator(std::ostream& _stream) : stream(_stream) 
    { } 

    int_type overflow(int_type c) 
    { 
     return stream.rdbuf()->sputc(c); 
    } 

    int sync() 
    { 
     if (stream.iword(separatorEnabled())) 
     { 
      void*& p = stream.pword(getSeparator()); 
      stream << *static_cast<std::string*>(p); 
      return 0; 
     } 
     return stream.rdbuf()->pubsync(); 
    } 
private: 
    std::ostream& stream; 
}; 

void cleanup(std::ios_base::event evt, std::ios_base& str, int idx) 
{ 
    if (str.iword(separatorEnabled()) && evt == std::ios_base::erase_event) 
    { 
     void*& p = str.pword(idx); 
     delete static_cast<std::string*>(p); 
     str.iword(separatorEnabled()) = false; 
    } 
} 

std::ostream& set_separator(std::ostream& os, const custom::sep_impl& manip) 
{ 
    if (!os.bad()) 
    { 
     os.pword(getSeparator()) = new std::string(manip.separator); 
     os.register_callback(cleanup, getSeparator()); 
    } 

    return os; 
} 

std::ostream& operator<<(std::ostream& os, const custom::sep_impl& manip) 
{ 
    std::ostream* p = os.tie(); 
    if (p && !p->iword(separatorEnabled()) 
    { 
     set_separator(*p, manip); 
     p->iword(separatorEnabled()) = true; 
    } 

    return os << std::unitbuf; 
} 

namespace custom 
{ 
    sep_impl::sep_impl(std::string const& _sep) : separator(_sep) { } 

    std::ostream& nosep(std::ostream& os) 
    { 
     cleanup(std::ios_base::erase_event, *os.tie(), getSeparator()); 
     os.tie(nullptr); 
     return os << std::nounitbuf; 
    } 

    void install_separator(std::ostream& o1, std::ostream& o2) 
    { 
     static custom_separator csep(o2); 
     o1.rdbuf(&csep); 
     o1.tie(&o2); 
    } 
} 

int main() 
{ 
    std::ostream os(nullptr); 
    custom::install_separator(os, std::cout); 

    os << custom::sep(", ") << 4 << 2 << custom::nosep; 
} 

我敢肯定還有改進的餘地,因此,如果任何人有任何建議,他們非常感謝。

Live Example

+0

這個解決方案更接近我最初的想法。安裝分離器似乎有點麻煩,但可能會簡化。謝謝! – kloffy

+0

@kloffy您可以將安裝程序轉換爲操縱器,以便您可以執行'os << custom :: install_separator(std :: cout)'。但無論如何高興我可以幫忙! :) – 0x499602D2

+0

是的,聽起來不錯。我希望我能接受多個答案,因爲這兩種解決方案都是有效的,並且各有利弊。不過,我稍微喜歡這種方法,所以我會接受它。再次感謝@Raxvan和@ 0x499602D2! – kloffy

2

好了,所以這是挑釁不乾淨/最短的解決方案,但在這裏是做這件事的一種方法:

namespace custom 
{ 
    struct sep 
    { 
     sep(const std::string & s) 
      :separator(s) 
     { 
     } 
     std::string separator; 
    }; 
} 
typedef std::basic_ostream<char, std::char_traits<char> > CoutType; 
typedef CoutType& (*StandardEndLine)(CoutType&); 
class SeparatorWrap 
{ 
public: 
    SeparatorWrap(std::ostream & _ofs, const custom::sep & s) 
     : ofs(_ofs) 
     , separator(s) 
    {} 
    template <class W> 
    SeparatorWrap& operator << (W && w) 
    { 
     ofs << separator.separator << w; 
     return (*this); 
    } 
    ostream & operator << (const StandardEndLine &) 
    { 
     //writing std::endl will remove the separator 
     return ofs << std::endl; 
    } 
protected: 
    std::ostream &   ofs; 
    custom::sep   separator; 
}; 
class SeparatorWrapFirst 
{ 
public: 
    SeparatorWrapFirst(std::ostream & _ofs, const custom::sep & s) 
     : ofs(_ofs) 
     , separator(s) 
    {} 
    template <class W> 
    SeparatorWrap  operator << (W && w) 
    { 
     ofs << w; 
     return SeparatorWrap(ofs, separator); 
    } 
    ostream &  operator << (const StandardEndLine &) 
    { 
     //writing std::endl will remove the separator 
     return ofs << std::endl; 
    } 
protected: 
    std::ostream &   ofs; 
    custom::sep   separator; 
}; 
SeparatorWrapFirst operator << (std::ostream & ofs,const custom::sep & s) 
{ 
    return SeparatorWrapFirst(ofs, s); 
} 


int main() 
{ 
    std::cout << custom::sep(", ") << 1 << "two" << 3 << std::endl; 
} 

這裏是它如何工作的:

std::cout << custom::sep(", ")返回類SeparatorWrapFirst型(使用全局operator <<)用於寫入一個沒有分隔符值的輸出。這是因爲如果你有一個元素,你不需要寫分隔符。

在調用SeparatorWrapFirst的第一個運算符<<之後,將返回SeparatorWrap類,並且也使用分隔符進行打印。這是爲了多個值。

編輯:

因此,從評論(@gexicide)看來,我的可能把custom manipulator inside std::cout。這可以讓你做這樣的事情:

std::cout << custom::sep(", "); 
std::cout << 1 << "two" << 3 << std::endl; 

哪裏從上面的第一個解決方案不會爲此工作。

+0

這裏的問題可能是下一次調用'std :: cout <<'會忘記分隔符。如果這種行爲是故意的,那麼這個解決方案就非常好。如果你想粘性行爲,看看這個答案:http://stackoverflow.com/a/13335615/1408611 – gexicide

+1

@gexicide是的,要解決這個問題,我現在看到的唯一方法是有一個自定義實現'std :: cout'裏面有額外的功能。但是對於這個解決方案來說是很多工作。 – Raxvan

+0

如果沒有自定義的cout實現,可以在我上面的鏈接中查看答案。 ios_base有一種方式存儲自定義的每個數據流。 – gexicide

相關問題