2013-02-13 42 views
0

考慮一下你負責爲標準庫容器寫一個簡單的漂亮打印工具的情況。在頭pretty_print.hpp聲明如下功能:正在使用'包裝包裝'被認爲是不好的做法/指示壞的設計?

// In pretty_print.hpp 
template<typename T> 
void pretty_print(std::ostream& os, std::vector<T> const& vec); 

template<typename T> 
void pretty_print(std::ostream& os, std::set<T> const& set); 

template<typename T, typename U> 
void pretty_print(std::ostream& os, std::map<T, U> const& map); 

// etc. 

然而,由於容器不能向前聲明,您必須#include每個容器頭。因此,包括pretty_print.hpp到你的庫的其他部分會(可能會導致相當多的代碼膨脹)。因此,爲了避免將這些依賴引入到其他編譯單元中,您會創建一大堆文件(我稱之爲'頭文件包裝器',因爲我找不到任何其他術語),稱爲print_vector.hppprint_set.hpp等等有一個類似的佈局:

// In print_vector.hpp 
#include <vector> 
template<typename T> 
void pretty_print(std::ostream& os, std::vector<T> const& vec); 

// In print_set.hpp 
#include <set> 
template<typename T> 
void pretty_print(std::ostream& os, std::set<T> const& set); 

// you get the point 

所以,當你想能夠pretty_print一個向量,你會#include print_vector.hpp,它將只介紹<vector>到當前編譯單元,而不是<set><map>或者你mightn任何其他頭不需要。請注意,我使用的是pretty_print作爲示例(我確信有很多優秀的方式可以打印容器),但還有其他一些原因可能需要執行此操作(例如,在製作lean_windows.h標頭「包裝器」之前您需要#define WIN32_LEAN_AND_MEAN包括windows.h)。

我看不出這種方法有什麼問題,因爲這意味着您要避免導致可能無法在編譯單元中使用/需要的一堆頭文件引起的可能的膨脹。儘管如此,它仍然覺得「錯誤」,因爲它對別人可能並不明顯,你的'包裝器'實際上包含了你想要的標題,並且似乎玷污了包括標準庫標題的'神聖性'(#include <string>是慣用的,而#include "string_wrapper.hpp"不是)。

這是否被認爲是不良做法\表示設計不好?

+0

在這個例子中,我不認爲你(或你的用戶)應想'print_vector.hpp'作爲對''包裝。碰巧,它確實包含'',但這不是*它的實際*,它用於聲明'pretty_print'的重載。如果有'''類似於'',那麼它會包括這個,相反,如果你想出了一個(不可移植的)方法來在特定的實現上向前聲明'vector',那麼你可以優化'print_vector'爲那個實現。一般用戶不應該依賴傳遞包含。 – 2013-02-13 12:08:24

+0

包括''不會在程序中引入任何額外的代碼(「代碼膨脹」),它可能會增加編譯每個.cpp文件所需的時間。 – 2013-02-13 12:31:00

+0

@SteveJessop:你對我的術語是正確的,也許我用過的例子並不是最好的。如果我包含一個包含一些編譯「選項」的大型庫頭文件(比如'windows.h'的'WIN32_LEAN_AND_MEAN'或'NOMINMAX'),那麼只包裝'#define's和'#include'就行了嗎?在頭文件中,只要它沒有用於暴露給用戶的文件中? @Bo:是的,你是對的。對於包含許多模板標題的後果,我不是百分之百的理解,但是從我讀過的內容的一般問題來看,我知道應儘可能避免這種情況。我想我有很多閱讀要做。 – 2013-02-13 12:43:33

回答

1

一些圖書館處理這類事情的一種方式是讓用戶決定。製作文件,如print/vector.hppprint/set.hpp,也可以製作一個像print/all.hpp(或只是print.hpp,雖然這可能會鼓勵壞習慣)。最後一個文件只包含所有單個文件,所以想要「方便」的人可以擁有它,那些想要精簡編譯的人也可以擁有。

的作品類似上述情況的一個常見的例子是Boost的智能指針庫:http://www.boost.org/doc/libs/release/boost/smart_ptr.hpp

+0

感謝您的鏈接。儘管我對boost有些熟悉,但我從未真正學過如何組織它們的頭文件,這似乎是我面臨的問題的優雅解決方案。 – 2013-02-13 12:37:00

+0

不客氣!這是另一個例子:http://www.boost.org/doc/libs/release/libs/exception/doc/boost_exception_all_hpp.html – 2013-02-13 12:41:35

0

我寧願問,是否真的有必要。你正在慢慢地陷入一種單一的標題的方向,這將改變你的#include部分成爲一個痛苦的混亂。

如果您想確定,不會將標識符與另一個標識符相混淆,只需使用名稱空間即可。

namespace PrettyPrint 
{ 
    template<typename T> 
    void pretty_print(std::ostream& os, std::vector<T> const& vec); 

    template<typename T> 
    void pretty_print(std::ostream& os, std::set<T> const& set); 

    template<typename T, typename U> 
    void pretty_print(std::ostream& os, std::map<T, U> const& map); 
} 

現在,這些功能可以避免與其他人誤解,您的代碼仍然安全和優雅。

我看到的唯一缺點是,包含長頭文件會使編譯過程更長一些。然而,我認爲,我們最多隻談十分之幾秒,所以如果你沒有像386這樣的工作,這應該不是一個大問題(更別提預編譯頭文件了,但老實說,我做了甚至沒有使用過一次)。

+0

命名空間如何解決他的問題? (關於編譯時間,如果他正在處理一個大型項目,有數百個源文件,並且每個文件都包含他的頭文件,那麼編譯時間的差異可能很大。) – 2013-02-13 12:35:22

+0

將頭文件分成單獨的文件意味着以減少代碼膨脹。命名空間也可以減少它(你實際上無法將一個標識符誤認爲另一個來自不同的頭文件),而不需要增加包含文件的數量。我認爲,在這種情況下,在這些情況下,問題也將得到解決。 – Spook 2013-02-13 12:46:41

+0

命名空間不會減少*翻譯單元*的大小,這是OP所擔心的。 – 2013-02-13 12:52:12

0

你可能只是嘗試更寬泛的版本:

template<class T> 
void print_element(std::ostream& os, T const& element) 
{ 
    os << T; 
} 
template<class Key,class Value> 
void print_element(std::ostream& os, std::pair<Key,Value> const& element) 
{ 
    os << '(' << element->first << ',' << element->second << ')'; 
} 

template<typename Container> 
void pretty_print(std::ostream& os, Container const& c) 
{ 
    for (auto i: c) 
    { 
    // print some stuff 
    print_element(os, *i); 
    // print other stuff 
    } 
} 
相關問題