2013-11-29 27 views
6

在閱讀了一篇關於基於策略的設計的文章並希望自己嘗試一些東西之後,我花了一些時間重新設計一次我曾經做過的記錄器類到基於策略的方法。使用記錄器的基於策略的方法

一些代碼:

template <class Filter, class Formatter, class Outputter> 
    class LoggerImpl : public LoggerBase { 
    public: 
     LoggerImpl(const Filter& filter = Filter(), const Formatter& formatter = Formatter(), const Outputter& outputter = Outputter()); 
     ~LoggerImpl(); 

     void log(int channel, int loglevel, const char* msg, va_list list) const; 
    private: 
     const Filter mFilter; 
     const Formatter mFormatter; 
     const Outputter mOutputter; 
    }; 

template <class Filter, class Formatter, class Outputter> 
LoggerImpl<Filter, Formatter, Outputter>::LoggerImpl(const Filter& filter, const Formatter& formatter, const Outputter& outputter) : 
      mFilter(filter), mFormatter(formatter), mOutputter(outputter) { 
       debuglib::logdispatch::LoggerMgr.addLogger(this); 
     } 

typedef LoggerImpl<NoFilter, SimpleFormatter, ConsoleOutputter> ConsoleLogger; 
typedef LoggerImpl<ChannelFilter, SimpleFormatter, VSOutputter> SimpleChannelVSLogger; 
typedef LoggerImpl<NoFilter, SimpleFormatter, FileOutputter> FileLogger; 

ConsoleLogger c; 
SimpleChannelVSLogger a(const ChannelFilter(1)); 
FileLogger f(NoFilter(), SimpleFormatter(), FileOutputter("log.txt")); 

// macro for sending log message to all current created loggers 
LOG(1, WARN, "Test %d", 1423); 

根據記錄我需要通過像SimpleChannelVsLogger內logchannel或FileOututter日誌文件的文件名的附加信息。

我將參數傳遞給LoggerImpl的構造函數作爲const引用,然後將它們複製到存儲在記錄器類中的對象中。 需要複製它們,因爲生命週期擴展不是通過將臨時創建的對象綁定到const引用時發生的函數參數傳遞的(此處更多:Does a const reference prolong the life of a temporary?)。

因此,第一件事情是:如果我不想使用指針,因爲我在使用模板時對運行時分配不感興趣,我想除了像上面那樣複製臨時創建的對象之外,沒有其他解決方案嗎?

複製東西中的實際問題現在與FileOutputter: 當然不能複製當然,所以如何複製FileOutputter對象包含流? 我想出了以下解決方案來克服這個問題:

struct FileOutputter { 
      // c_tor 
      FileOutputter() {} 

      // c_tor 
      explicit FileOutputter(const char* fname) { 
       mStream = std::make_shared<std::fstream>(fname, std::fstream::out); 
      } 

      // The copy c_tor will be invoked while creating any logger with FileOutputter 
      // FileLogger f(NoFilter(), SimpleFormatter(), FileOutputter("log.txt")); 
      // as all incoming paramters from the constructors stack frame are copied into the current instance of the logger 
      // as copying a file-stream is not permitted and not good under any means 
      // a shared pointer is used in the copy c_tor 
      // to keep the original stream until no reference is referring to it anymore 
      FileOutputter(const FileOutputter& other) { 
       mStream = other.mStream; 
      } 

      ~FileOutputter() { 
      } 

      void out(const char* msg) const { 
       *mStream << msg; 
      } 

      std::shared_ptr<std::fstream> mStream; 
     }; 

不知怎的,我有感覺,這似乎有點複雜,「簡單記錄器類」,然而,這可能只是一個「問題」與策略在這種情況下基於設計的方法。

有什麼想法,歡迎

+1

注意:你不應該重複的默認參數(「'=過濾器()'」等)的函數定義 –

+0

謝謝,我糾正了。 – Chris

回答

4

這是正確的,你應該複製的對象,如果您打算將它們存儲在你的類成員。

存儲引用是危險的,因爲它可能將臨時對象作爲參數傳遞給您的ctor,這會導致臨時引用在臨時對象被破壞時。

將參數作爲指針傳遞是一種替代方法,但是這種方法也有問題,因爲它可能會傳入一個nullptr(NULL值),並且您必須檢查這一點。

另一種替代方案是移動這些值即通過參數作爲r值參考。這樣可以避免複製,但是它會要求客戶在調用ctor時要麼通過臨時對象,要麼通過std::move對象。將不再可能傳遞l值參考。

// Define ctor as taking r-value references. 
template <class Filter, class Formatter, class Outputter> 
LoggerImpl<Filter, Formatter, Outputter>::LoggerImpl(Filter&& filter, Formatter&& formatter, Outputter&& outputter) : 
     mFilter(std::move(filter)), mFormatter(std::move(formatter)), mOutputter(std::move(outputter)) { 
      // ... 
} 

/* ... */ 

// Calling ctor. 
FileLogger f1(NoFilter(), SimpleFormatter(), FileOutputter("log.txt")); // OK, temporaries. 
FileOutputter fout("log.txt"); 
FileLogger f2(NoFilter(), SimpleFormatter(), fout); // Illegal, fout is l-value. 
FileLogger f3(NoFilter(), SimpleFormatter(), std::move(fout)); // OK, passing r-value. fout may not be used after this! 

如果您決定採取複製方法,那麼我建議您改用ctor中的值來傳遞參數。這將允許編譯器執行優化如複製elision(閱讀:Want Speed? Pass by Value)。

template <class Filter, class Formatter, class Outputter> 
LoggerImpl<Filter, Formatter, Outputter>::LoggerImpl(Filter filter, Formatter formatter, Outputter outputter) : 
     mFilter(std::move(filter)), mFormatter(std::move(formatter)), mOutputter(std::move(outputter)) { 
      // ... 
} 

使用上述的定義:在最好的情況下,編譯器將所述的Elid副本和成員將移動構造(通過臨時對象時)。

在最壞的情況下:將執行復制和移動構建(當傳遞l值時)。

使用您的版本(傳遞參數作爲const的參考),將會執行副本總是,因爲編譯器無法執行優化。

對於移動構建工作,您將不得不確保作爲參數傳遞的類型是可移動的(可以隱式地或使用聲明的移動ctor)。如果一個類型不是可移植的,它將被複制構建。

當涉及到複製在FileOutputter流,使用std::shared_ptr似乎是一個很好的解決方案,但是應該在初始化列表初始化mStream,而不是在構造函數體內分配:

explicit FileOutputter(const char* fname) 
    : mStream(std::make_shared<std::ofstream>(fname)) {} 

// Note: Use std::ofstream for writing (it has the out-flag enabled by default). 
//  There is another flag that may be of interest: std::ios::app that forces 
//  all output to be appended at the end of the file. Without this, the file 
//  will be cleared of all contents when it is opened. 

一個std::ofstream是不可複製的,並傳遞一個智能指針(確保使用std::shared_ptr雖然)可能是您的案例中最簡單的解決方案,並且在我看來,與您所說的相反,不是複雜的

另一種方法是使流成員靜態,但是然後每個實例FileOutputter將使用相同的std::ofstream對象,並且不可能使用寫入不同文件的並行記錄器對象等。

另外,您可以移動流爲std::ofstream是不可複製的,但移動。然而,這需要你做出FileOutputter可移動和不可複製(也可能LoggerImpl爲好),如使用「移動」的對象,比其析構函數等,可能會導致UB。製作一個管理僅移動類型的對象本身就變成了移動,但有時候可能會有很多意義。

std::ofstream out{"log.txt"}; 
std::ofstream out2{std::move(out)} // OK, std::ofstream is moveable. 
out2 << "Writing to stream";  // Ok. 
out << "Writing to stream";  // Illegal, out has had its guts ripped out. 

而且,在所提供的例子,你並不需要聲明的副本構造函數或析構函數用於FileOutputter,因爲他們將被編譯器隱式生成。

+0

感謝您提供非常詳細的信息!也感謝您指出使用初始化列表和std :: IOS的​​std :: shared_ptr的應用::方式爲ofstream的的。 Í通過編譯器瞭解隱式生成的副本c_tor和d_tor,之前只有一些調試輸出,並且我忘記刪除它。在使用shared_ptr命名之後,與其他可能的解決方案相比,看起來確實不復雜。我對可移動物體並不熟悉,但我也會研究這一點。 – Chris

3

你可以有策略類包含靜態功能,因此理想情況下,你會想FileOutputter的樣子:

template<std::string fileName> 
    struct FileOutputter { 

     static void out(const char* msg) const { 
      std::ofstream out(fileName); 
      out << msg; 
     } 
    }; 

您可以創建LoggerImpl的一個實例是這樣

LoggerImpl<NoFilter, SimpleFormatter, FileOutputter<"log.txt"> > FileLogger; 

這意味着您的LoggerImpl不需要存儲它只需調用其靜態函數的策略類的副本。 不幸的是,這不起作用,因爲你不能將字符串作爲模板參數,但是你可以建立一個字符串表並在字符串表中傳遞文件名的索引。所以,再一次,你會非常希望這個樣子:

//This class should be a singleton 
class StringManager 
{ 
    std::vector<std::string> m_string; 
public: 
    void addString(const std::string &str); 
    const std::string &getString(int index); 
    int getIndexOf(const std::string &str); 
}; 

然後你FileLogger會得到一個int作爲模板參數,這將是在StringManager字符串的索引。這也不會起作用,因爲您需要在編譯時可用的索引,並且StringManager將在運行時初始化。所以你必須手動構建字符串表並手動寫入字符串的索引。所以你的代碼看起來像(你做StringManager後單:

StringManager::instance()->addString("a"); 
StringManager::instance()->addString("b"); 
StringManager::instance()->addString("c"); 
StringManager::instance()->addString("d"); 
StringManager::instance()->addString("log.txt"); 
LoggerImpl<NoFilter, SimpleFormatter, FileOutputter<4> > FileLogger; 

你必須確保創建FileLogger的第一個實例之前StringManager完全初始化 這是一個有點難看,但使用模板和字符串有點難看。 你也可以這樣做:

template <class FilenameHolder> 
struct FileOutputter { 

     static void out(const char* msg) const { 
      std::ofstream out(FilenameHolder::fileName()); 
      out << msg; 
     } 
    }; 

class MyFileNameHolder 
{ 
    static std::string fileName() {return "log.txt";} 
}; 
+0

感謝您的輸入。我看了一下使用一個const char *作爲非類型模板參數,以及發現一些有用的東西在這裏http://stackoverflow.com/questions/5547852/string-literals-not-allowed-as-non-type-template α參數。不知怎的,這種做法似乎有點太哈克給我,我也真的不希望添加,只是因爲這個問題的StringManager。也許我會嘗試像曾經,但可能不是最終的解決辦法,如果我不需要StringManager在我的框架的東西另一種情況。 – Chris

+0

我寫了一個更漂亮的解決方案,也許你會更喜歡那個。 –