淺談C++ 物理設(shè)計(jì)

軟件設(shè)計(jì)是一個(gè)守破離的過程
  1. 設(shè)計(jì)原則
  2. 代碼規(guī)范
  3. 最佳實(shí)踐
  4. 附錄: 實(shí)用宏

設(shè)計(jì)原則

原則 基本含義
自滿足原則 頭文件本身是可以編譯通過的
單一職責(zé)原則 頭文件包含的實(shí)體的職責(zé)是單一的
最小依賴原則 絕不包含不必要的頭文件
最小可見性原則 盡量封裝隱藏類的成員

自滿足原則

所有頭文件都應(yīng)該自滿足的盟劫。看一個(gè)具體的示例代碼砖茸,這里定義了一個(gè)TestCase.h頭文件侣诵。TestCase對(duì)父類TestLeaf, TestFixture都存在編譯時(shí)依賴,但沒有包含基類的頭文件边苹。

反例:

// cppunit/TestCase.h
#ifndef EOPTIAWE_23908576823_MSLKJDFE_0567925
#define EOPTIAWE_23908576823_MSLKJDFE_0567925    

struct TestCase : TestLeaf, TestFixture
{
    TestCase(const std::string &name="");
    
private:
    OVERRIDE(void run(TestResult *result));
    OVERRIDE(std::string getName() const);

private:
    ABSTRACT(void runTest());
    
private:
    const std::string name;
};

#endif

為了滿足自滿足原則审丘,其自身必須包含其所有父類的頭文件。

正例:

// cppunit/TestCase.h
#ifndef EOPTIAWE_23908576823_MSLKJDFE_0567925
#define EOPTIAWE_23908576823_MSLKJDFE_0567925    

#include "cppunit/core/TestLeaf.h"
#include "cppunit/core/TestFixture.h"

struct TestCase : TestLeaf, TestFixture
{
    TestCase(const std::string &name="");
    
private:
    OVERRIDE(void run(TestResult &result));
    OVERRIDE(std::string getName() const);

private:
    ABSTRACT(void runTest());
    
private:
    const std::string name;
};

#endif

即使TestCase直接持有name的成員變量勾给,但沒有必要包含std::string的頭文件滩报,因?yàn)?code>TestCase覆寫了其父類的getName成員函數(shù),父類為了保證自滿足原則播急,自然已經(jīng)包含了std::string的頭文件脓钾。

同樣的原因,也沒有必要在此前置聲明TestResult桩警,因?yàn)楦割惪啥ㄒ呀?jīng)聲明過了可训。

單一職責(zé)

這是SRP(Single Reponsibility Priciple)在頭文件設(shè)計(jì)時(shí)的一個(gè)具體運(yùn)用。頭文件如果包含了其它不相關(guān)的元素捶枢,則包含該頭文件的所有實(shí)現(xiàn)文件都將被這些不相關(guān)的元素所污染握截,重編譯將成為一件高概率的事件。

如示例代碼烂叔,將OutputStream, InputStream同時(shí)定義在一個(gè)頭文件中谨胞,將違背該原則。本來只需只讀接口蒜鸡,無意中被只寫接口所污染胯努。

反例:

// io/Stream.h
#ifndef LDGOUIETA_437689Q20_ASIOHKFGP_980341
#define LDGOUIETA_437689Q20_ASIOHKFGP_980341

#include "base/Role.h"

DEFINE_ROLE(OutputStream)
{
    ABSTRACT(void write());
};

DEFINE_ROLE(InputStream)
{
    ABSTRACT(void read());
};

#endif

正例: 先創(chuàng)建一個(gè)OutputStream.h文件:

// io/OutputStream.h
#ifndef LDGOUIETA_437689Q20_ASIOHKFGP_010234
#define LDGOUIETA_437689Q20_ASIOHKFGP_010234
    
#include "base/Role.h"

DEFINE_ROLE(OutputStream)
{
    ABSTRACT(void write());
};

#endif

再創(chuàng)建一個(gè)InputStream.h文件:

// io/InputStream.h
#ifndef LDGOUIETA_437689Q20_ASIOHKFGP_783621
#define LDGOUIETA_437689Q20_ASIOHKFGP_783621

#include "base/Role.h"

DEFINE_ROLE(InputStream)
{
    ABSTRACT(void read());
};

#endif

最小依賴

一個(gè)頭文件只應(yīng)該包含必要的實(shí)體牢裳,尤其在頭文件中僅僅對(duì)實(shí)體的聲明產(chǎn)生依賴,那么前置聲明是一種有效的降低編譯時(shí)依賴的技術(shù)叶沛。

反例:

// cppunit/Test.h
#ifndef PERTP_8792346_QQPKSKJ_09472_HAKHKAIE
#define PERTP_8792346_QQPKSKJ_09472_HAKHKAIE

#include <base/Role.h>
#include <cppunit/core/TestResult.h>
#include <string>

DEFINE_ROLE(Test)
{
    ABSTRACT(void run(TestResult& result));
    ABSTRACT(int countTestCases() const);
    ABSTRACT(int getChildTestCount() const);
    ABSTRACT(std::string getName() const);
};

#endif

如示例代碼蒲讯,定義了一個(gè)xUnit框架中的Test頂級(jí)接口,其對(duì)TestResult的依賴僅僅是一個(gè)聲明依賴灰署,并沒有必要包含TestResult.h判帮,前置聲明是解開這類編譯依賴的鑰匙。

值得注意的是溉箕,對(duì)標(biāo)準(zhǔn)庫(kù)std::string的依賴脊另,即使它僅作為返回值,但因?yàn)樗鼘?shí)際上是一個(gè)typedef约巷,所以必須老實(shí)地包含其對(duì)應(yīng)的頭文件偎痛。事實(shí)上,如果產(chǎn)生了對(duì)標(biāo)準(zhǔn)庫(kù)名稱的依賴独郎,基本上都需要包含對(duì)應(yīng)的頭文件踩麦。

另外,對(duì)DEFINE_ROLE宏定義的依賴則需要包含相應(yīng)的頭文件氓癌,以便實(shí)現(xiàn)該頭文件的自滿足谓谦。

但是,TestResult僅作為成員函數(shù)的參數(shù)出現(xiàn)在頭文件中贪婉,所以對(duì)TestResult的依賴只需前置聲明即可反粥。

正例:

// cppunit/Test.h
#ifndef PERTP_8792346_QQPKSKJ_09472_HAKHKAIE
#define PERTP_8792346_QQPKSKJ_09472_HAKHKAIE

#include <base/Role.h>
#include <string>

struct TestResult;

DEFINE_ROLE(Test)
{
    ABSTRACT(void run(TestResult& result));
    ABSTRACT(int countTestCases() const);
    ABSTRACT(int getChildTestCount() const);
    ABSTRACT(std::string getName() const);
};

#endif

在選擇包含頭文件還是前置聲明時(shí),很多程序員感到迷茫疲迂。其實(shí)規(guī)則很簡(jiǎn)單才顿,在如下場(chǎng)景前置聲明即可,無需包含頭文件:

  • 指針
  • 引用
  • 返回值
  • 函數(shù)參數(shù)

相反地尤蒿,如果編譯器需要知道實(shí)體的真正內(nèi)容時(shí)郑气,則必須包含頭文件,此依賴也常常稱為強(qiáng)編譯時(shí)依賴腰池。強(qiáng)編譯時(shí)依賴主要包括如下幾種場(chǎng)景:

  • typedef定義的實(shí)體
  • 繼承
  • inline
  • template
  • 引用類內(nèi)部成員時(shí)
  • sizeof運(yùn)算

最小可見性

在頭文件中定義一個(gè)類時(shí)尾组,清晰、準(zhǔn)確的public, protected, private是傳遞設(shè)計(jì)意圖的指示燈示弓。其中private做為一種實(shí)現(xiàn)細(xì)節(jié)被隱藏起來讳侨,為適應(yīng)未來不明確的變化提供便利的措施。

不要將所有的實(shí)體都public奏属,這無疑是一種自殺式做法跨跨。應(yīng)該以一種相反的習(xí)慣性思維,盡最大可能性將所有實(shí)體private拍皮,直到你被迫不得不這么做為止歹叮,依次放開可見性的權(quán)限。

如下例代碼所示铆帽,按照public-private, function-data依次排列類的成員咆耿,并對(duì)具有相同特征的成員歸類,將大大改善類的整體布局爹橱,給讀者留下清晰的設(shè)計(jì)意圖萨螺。

反例:

//trans-dsl/sched/SimpleAsyncAction.h
#ifndef IUOTIOUQW_NMAKLKLG_984592_KJSDKLJFLK
#define IUOTIOUQW_NMAKLKLG_984592_KJSDKLJFLK

#include "trans-dsl/action/Action.h"
#include "trans-dsl/utils/EventHandlerRegistry.h"

struct SimpleAsyncAction : Action
{
    template<typename T>
    Status waitOn(const EventId eventId, T* thisPointer,
             Status (T::*handler)(const TransactionInfo&, const Event&), 
             bool forever = false)
    {
        return registry.addHandler(eventId, thisPointer, handler, forever);
    }

    Status waitUntouchEvent(const EventId eventId);

    OVERRIDE(Status handleEvent(const TransactionInfo&, const Event&));
    OVERRIDE(void kill(const TransactionInfo&, const Status)); 

    DEFAULT(void, doKill(const TransactionInfo&, const Status));

    EventHandlerRegistry registry;
};

#endif

正例:

// trans-dsl/sched/SimpleAsyncAction.h
#ifndef IUOTIOUQW_NMAKLKLG_984592_KJSDKLJFLK
#define IUOTIOUQW_NMAKLKLG_984592_KJSDKLJFLK

#include "trans-dsl/action/Action.h"
#include "trans-dsl/utils/EventHandlerRegistry.h"

struct SimpleAsyncAction : Action
{
    template<typename T>
    Status waitOn(const EventId eventId, T* thisPointer,
             Status (T::*handler)(const TransactionInfo&, const Event&), 
             bool forever = false)
    {
        return registry.addHandler(eventId, thisPointer, handler, forever);
    }

    Status waitUntouchEvent(const EventId eventId);

private:
    OVERRIDE(Status handleEvent(const TransactionInfo&, const Event&));
    OVERRIDE(void kill(const TransactionInfo&, const Status)); 

private:
    DEFAULT(void, doKill(const TransactionInfo&, const Status));

private:
    EventHandlerRegistry registry;
};

#endif

代碼規(guī)范

頭文件保護(hù)宏

每一個(gè)頭文件都應(yīng)該具有獨(dú)一無二的保護(hù)宏,并保持命名規(guī)則的一致性愧驱,其中命名規(guī)則包括兩種風(fēng)格:

  • INCL_<PROJECT>_<MODULE>_<FILE>_H
  • 全局唯一的隨機(jī)序列碼

第一種命名規(guī)則問題在于:當(dāng)文件名重命名或移動(dòng)目錄時(shí)慰技,需要同步修改頭文件保護(hù)宏;推薦使用IDE隨機(jī)自動(dòng)地生成頭文件保護(hù)宏组砚,其更加快捷吻商、簡(jiǎn)單、安全糟红、有效艾帐。

反例:

// thread/Runnable.h
// 因名稱太短,存在名字沖突的可能性
#ifndef RUNNABLE_H
#define RUNNABLE_H

#include "base/Role.h"

DEFINE_ROLE(Runnable)
{
    ABSTRACT(void run());
};

#endif

正例:

// cppunit/AutoRegisterSuite.h
#ifndef INCL_CPPUNIT_AUTO_REGISTER_SUITE_H
#define INCL_CPPUNIT_AUTO_REGISTER_SUITE_H

#include "base/Role.h"

struct TestSuite;

DEFINE_ROLE(AtuoRegisterSuite)
{
    ABSTRACT(void add(TestSuite&));
};

#endif

正例:

// cppunit/AutoRegisterSuite.h
// IDE自動(dòng)生成
#ifndef INCL_ADCM_LLL_3465_DCPOE_ACLDDDE_479_YTEY_H
#define INCL_ADCM_LLL_3465_DCPOE_ACLDDDE_479_YTEY_H

#include "base/Role.h"

struct TestSuite;

DEFINE_ROLE(AtuoRegisterSuite)
{
    ABSTRACT(void add(TestSuite&));
};

#endif

下劃線與駝峰

路徑名一律使用小寫盆偿、下劃線或中劃線風(fēng)格的名稱柒爸;文件名應(yīng)該與程序主要實(shí)體名稱相同,可以使用駝峰命名事扭,也可以使用小寫捎稚、下劃線或中劃線分割的名字;實(shí)現(xiàn)文件的名字必須和頭文件保持一致求橄;包含頭文件時(shí)今野,必須保持路徑名、文件名大小寫敏感罐农。

反例:

// 路徑名htmlParser使用了駝峰命名風(fēng)格
#include "htmlParser/core/Attribute.h"

正例:

// 正確的頭文件包含
#include "html-parser/core/Attribute.h"
#include "yaml_parser.h"

最后腥泥,值得注意的是,團(tuán)隊(duì)內(nèi)必須保持一致的命名風(fēng)格啃匿。

大小寫敏感

包含頭文件時(shí)蛔外,必須保持路徑名、文件名大小寫敏感溯乒。因?yàn)樵赲ascii{Windows}夹厌,其大小寫不敏感,編譯時(shí)檢查失效裆悄,代碼失去了可移植性矛纹,所以在包含頭文件時(shí)必須保持文件名的大小寫敏感。

假如存在兩個(gè)物理文件名分別為SynchronizedObject.h, yaml_parser.h的兩個(gè)文件光稼。

反例:

// 路徑名或南、文件名大小寫與真實(shí)物理路徑孩等、物理文件名稱不符
#include "CppUnit/Core/SynchronizedObject.h"
#include "YAML_Parser.h"

正例:

#include "cppunit/core/SynchronizedObject.h"
#include "yaml_parser.h"

最后,值得注意的是采够,團(tuán)隊(duì)內(nèi)必須保持一致的命名風(fēng)格肄方。

分隔符

包含頭文件時(shí),路徑分隔符一律使用Unix風(fēng)格蹬癌,拒絕使用Windows風(fēng)格权她;即采用/而不是使用\分割路徑。

反例:

// 使用了Windows風(fēng)格的路徑分割符
#include "cppunit\core\SynchronizedObject.h"

正例:

// 使用了Unix風(fēng)格的路徑分割符
#include "cppunit/core/SynchronizedObject.h"

extern "C"

使用extern "C"時(shí)逝薪,不要包括include語句隅要。

反例:

//oss/oss_memery.h
#ifndef HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8
#define HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8
    
#ifdef  __cplusplus
extern "C" {
#endif

// 錯(cuò)誤地將include放在了extern "C"中
#include "oss_common.h"

void* oss_alloc(size_t);
void  oss_free(void*);

#ifdef  __cplusplus
}
#endif

#endif

正例:

//oss/oss_memery.h
#ifndef HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8
#define HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8
    
#include "oss_common.h"

#ifdef  __cplusplus
extern "C" {
#endif

void* oss_alloc(size_t);
void  oss_free(void*);

#ifdef  __cplusplus
}
#endif

#endif

兼容性

當(dāng)以C提供實(shí)現(xiàn)時(shí),頭文件中必須使用extern "C"聲明董济,以便支持C++的擴(kuò)展步清。

反例:

// oss/oss_memery.h
#ifndef HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8
#define HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8    

#include "oss_common.h"

void* oss_alloc(size_t);
void  oss_free(void*);

#endif

正例:

// oss/oss_memery.h
#ifndef HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8
#define HF0916DFB_1CD1_4811_B82B_9B8EB1A007D8    

#include "oss_common.h"

#ifdef  __cplusplus
extern "C" {
#endif

void* oss_alloc(size_t);
void  oss_free(void*);

#ifdef  __cplusplus
}
#endif

#endif

上帝頭文件

拒絕創(chuàng)建巨型頭文件,將所有實(shí)體聲明都放到頭文件中虏肾,而僅僅將外部依賴的實(shí)體聲明放到頭文件中尼啡。

信息隱藏

實(shí)現(xiàn)文件也是一種信息隱藏的慣用技術(shù),如果一些程序的實(shí)體不對(duì)外所依賴询微,則放在自己的實(shí)現(xiàn)文件中崖瞭,一則可降低依賴關(guān)系,二則實(shí)現(xiàn)更好的信息隱藏撑毛。

對(duì)于上帝頭文件书聚,其很多聲明和定義本來是不應(yīng)該放到頭文件,而應(yīng)該放會(huì)實(shí)現(xiàn)文件以便實(shí)現(xiàn)更好地信息隱藏藻雌。

編譯時(shí)依賴

巨型頭文件必然造成了巨大的編譯時(shí)依賴雌续,不僅僅帶來巨大的編譯時(shí)開銷,更重要的是這樣的設(shè)計(jì)將太多的實(shí)現(xiàn)細(xì)節(jié)暴露給用戶胯杭,導(dǎo)致后續(xù)版本兼容性的問題驯杜,阻礙了頭文件進(jìn)一步演進(jìn)、修改做个、擴(kuò)展的可能性鸽心,從而失去了軟件的可擴(kuò)展性。

include順序依賴

不要認(rèn)為提供一個(gè)大而全的頭文件會(huì)給你的用戶帶來方便居暖,用戶因此而更加困擾顽频。對(duì)于一個(gè)巨大的頭文件,其依賴關(guān)系很難一眼看清楚太闺,其自滿足性很難得到保證糯景,用戶在包含此頭文件時(shí),還要關(guān)心頭文件之間的依賴關(guān)系,甚至關(guān)心include語句的順序蟀淮,但這樣的代碼實(shí)現(xiàn)是及其脆弱的最住。

最佳實(shí)踐

自滿足驗(yàn)證

為了驗(yàn)證頭文件設(shè)計(jì)的自滿足原則,實(shí)現(xiàn)文件的第一條語句必然是包含其對(duì)應(yīng)的頭文件怠惶。

反例:

// cppunit/TestCase.cpp
#include "cppunit/core/TestResult.h"
#include "cppunit/core/Functor.h"
// 錯(cuò)誤:沒有放在第一行涨缚,無法校驗(yàn)其自滿足性
#include "cppunit/core/TestCase.h"

namespace
{
    struct TestCaseMethodFunctor : Functor
    {
        typedef void (TestCase::*Method)();
    
        TestCaseMethodFunctor(TestCase &target, Method method)
           : target(target), method(method)
        {}
    
        bool operator()() const
        {
            target.*method();
            return true;
        }
    
    private:
        TestCase ?
        Method method;
    };
}

void TestCase::run(TestResult &result)
{
    result.startTest(*this);
  
    if (result.protect(TestCaseMethodFunctor(*this, &TestCase::setUp)))
    {
        result.protect(TestCaseMethodFunctor(*this, &TestCase::runTest)); 
    }

    result.protect(TestCaseMethodFunctor(*this, &TestCase::tearDown));

    result.endTest(*this);
}

...

正例:

// cppunit/TestCase.cpp
#include "cppunit/core/TestCase.h"
#include "cppunit/core/TestResult.h"
#include "cppunit/core/Functor.h"

namespace
{
    struct TestCaseMethodFunctor : Functor
    {
        typedef void (TestCase::*Method)();
    
        TestCaseMethodFunctor(TestCase &target, Method method)
           : target(target), method(method)
        {}
    
        bool operator()() const
        {
            target.*method();
            return true;
        }
    
    private:
        TestCase ?
        Method method;
    };
}

void TestCase::run(TestResult &result)
{
    result.startTest(*this);
  
    if (result.protect(TestCaseMethodFunctor(*this, &TestCase::setUp))
    {
        result.protect(TestCaseMethodFunctor(*this, &TestCase::runTest)); 
    }

    result.protect(TestCaseMethodFunctor(*this, &TestCase::tearDown));

    result.endTest(*this);
}

...

overrideprivate

所有override的函數(shù)(除overridevirtual析構(gòu)函數(shù)之外)都應(yīng)該是private的,以保證按接口編程的良好設(shè)計(jì)原則甚疟。

反例:

// html-parser/filter/AndFilter.h
#ifndef EOIPWORPIO_06123124_NMVBNSDHJF_497392
#define EOIPWORPIO_06123124_NMVBNSDHJF_497392

#include "html-parser/filter/NodeFilter.h"
#include <list>

struct AndFilter : NodeFilter
{
     void add(NodeFilter*);

     // 設(shè)計(jì)缺陷:本應(yīng)該private
     OVERRIDE(bool accept(const Node&) const);

private:
     std::list<NodeFilter*> filters;
};

#endif

正例:

// html-parser/filter/AndFilter.h
#ifndef EOIPWORPIO_06123124_NMVBNSDHJF_497392
#define EOIPWORPIO_06123124_NMVBNSDHJF_497392

#include "html-parser/filter/NodeFilter.h"
#include <list>

struct AndFilter : NodeFilter
{
     void add(NodeFilter*);

private:
     OVERRIDE(bool accept(const Node&) const);

private:
     std::list<NodeFilter*> filters;
};

#endif

inline

避免頭文件中inline

頭文件中避免定義inline函數(shù)仗岖,除非性能報(bào)告指出此函數(shù)是性能的關(guān)鍵瓶頸逃延。

C++語言將聲明和實(shí)現(xiàn)進(jìn)行分離览妖,程序員為此不得不在頭文件和實(shí)現(xiàn)文件中重復(fù)地對(duì)函數(shù)進(jìn)行聲明。這是C/C++天生給我們的設(shè)計(jì)帶來的重復(fù)揽祥。這是一件痛苦的事情讽膏,驅(qū)使部分程序員直接將函數(shù)實(shí)現(xiàn)為inline

inline函數(shù)的代碼作為一種不穩(wěn)定的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)拄丰,被放置在頭文件里府树,其變更所導(dǎo)致的大面積的重新編譯是個(gè)大概率事件,為改善微乎其微的函數(shù)調(diào)用性能與其相比將得不償失料按。

除非有相關(guān)profiling性能測(cè)試報(bào)告奄侠,表明這部分關(guān)鍵的熱點(diǎn)代碼需要被放回頭文件中。

但需要注意在特殊的情況载矿,可以將實(shí)現(xiàn)inline在頭文件中垄潮,因?yàn)闉樗鼈儎?chuàng)建實(shí)現(xiàn)文件過于累贅和麻煩。

  • virtual析構(gòu)函數(shù)
  • 空的virtual函數(shù)實(shí)現(xiàn)
  • C++11default函數(shù)
鼓勵(lì)實(shí)現(xiàn)文件中inline

對(duì)于在編譯單元內(nèi)部定義的類而言闷盔,因?yàn)樗目蛻魯?shù)量是確定的弯洗,就是它本身。另外逢勾,由于它本來就定義在源代碼文件中牡整,因此并沒有增加任何“物理耦合”。所以溺拱,對(duì)于這樣的類逃贝,我們大可以將其所有函數(shù)都實(shí)現(xiàn)為inline的,就像寫Java代碼那樣迫摔,Once & Only Once秋泳。

以單態(tài)類的一種實(shí)現(xiàn)技術(shù)為例,講解編譯時(shí)依賴的解耦與匿名命名空間的使用攒菠。(首先迫皱,應(yīng)該抵制單態(tài)設(shè)計(jì)的誘惑,單態(tài)其本質(zhì)是面向?qū)ο蠹夹g(shù)中全局變量的替代品。濫用單態(tài)模式卓起,猶如濫用全局變量和敬,是一種典型的設(shè)計(jì)壞味道。只有確定在系統(tǒng)中唯一存在的概念戏阅,才能使用單態(tài)模式)昼弟。

實(shí)現(xiàn)單態(tài),需要對(duì)系統(tǒng)中唯一存在的概念進(jìn)行封裝奕筐;但這個(gè)概念往往具有巨大的數(shù)據(jù)結(jié)構(gòu)舱痘,如果將其聲明在頭文件中,無疑造成很大的編譯時(shí)依賴离赫。

反例:

// ne/NetworkElementRepository.h
#ifndef UIJVASDF_8945873_YUQWTYRDF_85643
#define UIJVASDF_8945873_YUQWTYRDF_85643    

#include "base/Status.h"
#include "base/BaseTypes.h"
#include "transport/ne/NetworkElement.h"
#include <vector>

struct NetworkElementRepository
{
    static NetworkElement& getInstance();

    Status add(const U16 id);
    Status release(const U16 id);
    Status modify(const U16 id);
    
private:
    typedef std::vector<NetworkElement> NetworkElements;
    NetworkElements elements;
};

#endif

受文章篇幅的所限芭逝,NetworkElement.h未列出所有代碼實(shí)現(xiàn),但我們知道NetworkElement擁有巨大的數(shù)據(jù)結(jié)構(gòu)渊胸,上述設(shè)計(jì)導(dǎo)致所有包含NetworkElementRepository的頭文件都被NetworkElement所間接污染旬盯。

此時(shí),其中可以將依賴置入到實(shí)現(xiàn)文件中翎猛,解除揭開其嚴(yán)重的編譯時(shí)依賴胖翰。更重要的是,它更好地遵守了按接口編程的原則切厘,改善了軟件的擴(kuò)展性萨咳。

正例:

// ne/NetworkElementRepository.h
#ifndef UIJVASDF_8945873_YUQWTYRDF_85643
#define UIJVASDF_8945873_YUQWTYRDF_85643    

#include "base/Status.h"
#include "base/BaseTypes.h"
#include "base/Role.h"

DEFINE_ROLE(NetworkElementRepository)
{
    static NetworkElementRepository& getInstance();

    ABSTRACT(Status add(const U16 id));
    ABSTRACT(Status release(const U16 id));
    ABSTRACT(Status modify(const U16 id));
};

#endif

其實(shí)現(xiàn)文件包含NetworkElement.h,將對(duì)其的依賴控制在本編譯單元內(nèi)部疫稿。

// ne/NetworkElementRepository.cpp}]
#include "transport/ne/NetworkElementRepository.h"
#include "transport/ne/NetworkElement.h"
#include <vector>

namespace
{
    struct NetworkElementRepositoryImpl : NetworkElementRepository
    {
        OVERRIDE(Status add(const U16 id))
        {
            // inline implements
        }

        OVERRIDE(Status release(const U16 id))
        {
            // inline implements
        }

        OVERRIDE(Status modify(const U16 id))
        {
            // inline implements
        }
    
    private:
        typedef std::vector<NetworkElement> NetworkElements;
        NetworkElements elements;
    };
}

NetworkElementRepository& NetworkElementRepository::getInstance()
{
    static NetworkElementRepositoryImpl inst;
    return inst;
}

此處培他,對(duì)NetworkElementRepositoryImpl類的依賴是非常明確的,僅本編譯單元內(nèi)而克,所有可以直接進(jìn)行inline靶壮,從而簡(jiǎn)化了很多實(shí)現(xiàn)。

匿名namespace

匿名namespace的存在常常被人遺忘员萍,但它的確是一個(gè)利器腾降。匿名namespace的存在,使得所有受限于編譯單元內(nèi)的實(shí)體擁有了明確的處所碎绎。

自此之后螃壤,所有C風(fēng)格并局限于編譯單元內(nèi)的static函數(shù)和變量;以及類似Java中常見的private static的提取函數(shù)將常常被匿名namespace替代筋帖。

請(qǐng)記住匿名命名空間也是一種重要的信息隱藏技術(shù)奸晴。在實(shí)現(xiàn)文件中提倡使用匿名namespace, 以避免潛在的命名沖突。

如上例日麸,NetworkElementRepository.cpp通過匿名namespace寄啼,極大地減低了其頭文件的編譯時(shí)依賴逮光。

struct VS. class

除了名字不同之外,classstruct唯一的差別是:默認(rèn)可見性墩划。這體現(xiàn)在定義和繼承時(shí)涕刚。struct在定義一個(gè)成員,或者繼承時(shí)乙帮,如果不指明杜漠,則默認(rèn)為public,而class則默認(rèn)為private察净。

但這些都不是重點(diǎn)驾茴,重點(diǎn)在于定義接口和繼承時(shí),冗余public修飾符總讓人不舒服氢卡。簡(jiǎn)單設(shè)計(jì)四原則告訴告訴我們锈至,所有冗余的代碼都應(yīng)該被剔除。

但很多人會(huì)認(rèn)為structC遺留問題异吻,應(yīng)該避免使用裹赴。但這不是問題喜庞,我們不應(yīng)該否認(rèn)在寫C++程序時(shí)诀浪,依然在使用著很多C語言遺留的特性。關(guān)鍵在于延都,我們使用的是C語言中能給設(shè)計(jì)帶來好處的特性雷猪,何樂而不為呢?

正例:

// hamcrest/SelfDescribing.h
#ifndef OIWER_NMVCHJKSD_TYT_48457_GSDFUIE
#define OIWER_NMVCHJKSD_TYT_48457_GSDFUIE

struct Description;

struct SelfDescribing
{
    virtual void describeTo(Description& description) const = 0;
    virtual ~SelfDescribing() {}
};

#endif

反例:

// hamcrest/SelfDescribing.h
#ifndef OIWER_NMVCHJKSD_TYT_48457_GSDFUIE
#define OIWER_NMVCHJKSD_TYT_48457_GSDFUIE

class Description;

class SelfDescribing
{
public:
    virtual void describeTo(Description& description) const = 0;
    virtual ~SelfDescribing() {}
};

#endif

更重要的是晰房,我們確信“抽象”和“信息隱藏”對(duì)于軟件的重要性求摇,這促使我將public接口總置于類的最前面成為我們的首選,class的特性正好與我們的期望背道而馳(class的特性正好適合于將數(shù)據(jù)結(jié)構(gòu)捧為神物的程序員殊者,它們常常將數(shù)據(jù)結(jié)構(gòu)置于類聲明的最前面与境。)

不管你信仰那一個(gè)流派,切忌不能混合使用classstruct猖吴。在大量使用前導(dǎo)聲明的情況下摔刁,一旦一個(gè)使用struct的類改為class,所有的前置聲明都需要修改海蔽。

萬惡的struct tag

定義C風(fēng)格的結(jié)構(gòu)體時(shí)共屈,struct tag徹底抑制了結(jié)構(gòu)體前置聲明的可能性,從而阻礙了編譯優(yōu)化的空間党窜。

反例:

// radio/domain/Cell.h
#ifndef AQTYER_023874_NMHSFHKE_7432378293
#define AQTYER_023874_NMHSFHKE_7432378293

typedef struct tag_Cell
{
    WORD16 wCellId;
    WORD32 dwDlArfcn;
} T_Cell;

#endif
// radio/domain/Cell.h
#ifndef AQTYER_023874_NMHSFHKE_7432378293
#define AQTYER_023874_NMHSFHKE_7432378293

typedef struct
{
    WORD16 wCellId;
    WORD32 dwDlArfcn;
} T_Cell;

#endif

為了兼容C并為結(jié)構(gòu)體前置聲明提供便利拗引,如下解法是最合適的。

正例:

// radio/domain/Cell.h
#ifndef AQTYER_023874_NMHSFHKE_7432378293
#define AQTYER_023874_NMHSFHKE_7432378293

typedef struct T_Cell
{
    WORD16 wCellId;
    WORD32 dwDlArfcn;
} T_Cell;

#endif

需要注意的是幌衣,在C語言中矾削,如果沒有使用typedef,則定義一個(gè)結(jié)構(gòu)體的指針,必須顯式地加上struct關(guān)鍵字:struct T_Cell *pcell哼凯,而C++沒有這方面的要求垦细。

PIMPL

如果性能不是關(guān)鍵問題,考慮使用PIMPL降低編譯時(shí)依賴挡逼。

反例:

// mockcpp/ApiHook.h
#ifndef OIWTQNVHD_10945_HDFIUE_23975_HFGA
#define OIWTQNVHD_10945_HDFIUE_23975_HFGA

#include "mockcpp/JmpOnlyApiHook.h"

struct ApiHook
{
    ApiHook(const void* api, const void* stub)
      : stubHook(api, stub)
    {}

private:
    JmpOnlyApiHook stubHook;
};

#endif

正例:

// mockcpp/ApiHook.h
#ifndef OIWTQNVHD_10945_HDFIUE_23975_HFGA
#define OIWTQNVHD_10945_HDFIUE_23975_HFGA

struct ApiHookImpl;

struct ApiHook
{
    ApiHook(const void* api, const void* stub);
    ~ApiHook();

private:
    ApiHookImpl* This;
};

#endif
// mockcpp/ApiHook.cpp
#include "mockcpp/ApiHook.h"
#include "mockcpp/JmpOnlyApiHook.h"

struct ApiHookImpl
{
   ApiHookImpl(const void* api, const void* stub)
     : stubHook(api, stub)
   {
   }

   JmpOnlyApiHook stubHook;
};

ApiHook::ApiHook( const void* api, const void* stub)
  : This(new ApiHookImpl(api, stub))
{
}

ApiHook::~ApiHook()
{
    delete This;
}

通過ApiHookImpl* This的橋接括改,在頭文件中解除了對(duì)JmpOnlyApiHook的依賴,將其依賴控制在本編譯單元內(nèi)部家坎。

template

編譯時(shí)依賴

當(dāng)選擇模板時(shí)嘱能,不得不將其實(shí)現(xiàn)定義在頭文件中。當(dāng)編譯時(shí)依賴開銷非常大時(shí)虱疏,編譯模板將成為一種負(fù)擔(dān)惹骂。設(shè)法降低編譯時(shí)依賴,不僅僅為了縮短編譯時(shí)間做瞪,更重要的是為了得到一個(gè)低耦合的實(shí)現(xiàn)对粪。

反例:

// oss/OssSender.h
#ifndef HGGAOO_4611330_NMSDFHW_86794303_HJHASI
#define HGGAOO_4611330_NMSDFHW_86794303_HJHASI

#include "pub_typedef.h"
#include "pub_oss.h"
#include "oss_comm.h"
#include "pub_commdef.h"
#include "base/Assertions.h"
#include "base/Status.h"

struct OssSender
{
    OssSender(const PID& pid, const U8 commType)
      : pid(pid), commType(commType)
    {
    }
    
    template <typename MSG>
    Status send(const U16 eventId, const MSG& msg)
    {
        DCM_ASSERT_TRUE(OSS_SendAsynMsg(eventId, &msg, sizeof(msg), commType,(PID*)&pid) == OSS_SUCCESS); 
        return DCM_SUCCESS;
    }

private:
    PID pid;
    U8 commType;
};

#endif

為了實(shí)現(xiàn)模板函數(shù)send,將OSS的一些實(shí)現(xiàn)細(xì)節(jié)暴露到了頭文件中装蓬,包含OssSender.h的所有文件將無意識(shí)地產(chǎn)生了對(duì)OSS頭文件的依賴著拭。

提取一個(gè)私有的send函數(shù),并將對(duì)OSS的依賴移入到OssSender.cpp中牍帚,對(duì)PID依賴通過前置聲明解除儡遮,最終實(shí)現(xiàn)如代碼所示。

正例:

// oss/OssSender.h
#ifndef HGGAOO_4611330_NMSDFHW_86794303_HJHASI
#define HGGAOO_4611330_NMSDFHW_86794303_HJHASI

#include "base/Status.h"
#include "base/BaseTypes.h"

struct PID;

struct OssSender
{
    OssSender(const PID& pid, const U16 commType)
      : pid(pid), commType(commType) 
    {
    }
    
    template <typename MSG>
    Status send(const U16 eventId, const MSG& msg)
    {
        return send(eventId, (const void*)&msg, sizeof(MSG));    
    }

private:
    Status send(const U16 eventId, const void* msg, size_t size);

private:
    const PID& pid;
    U8 commType;
};

#endif

識(shí)別哪些與泛型相關(guān)暗赶,哪些與泛型無關(guān)的知識(shí)鄙币,并解開此類編譯時(shí)依賴是C++程序員的必備之技。

顯式模板實(shí)例化

模板的編譯時(shí)依賴存在兩個(gè)基本模型:包含模型蹂随,export模型十嘿。export模型受編譯技術(shù)實(shí)現(xiàn)的挑戰(zhàn),最終被C++11標(biāo)準(zhǔn)放棄岳锁。

此時(shí)绩衷,似乎我們只能選擇包含模型。其實(shí)浸锨,存在一種特殊的場(chǎng)景唇聘,適時(shí)選擇顯式模板實(shí)例化(Explicit Template Instantiated),降低模板的編譯時(shí)依賴柱搜。是能做到降低模板編譯時(shí)依賴的迟郎。

反例:

// quantity/Quantity.h
#ifndef HGGQMVJK_892302_NGFSLEU_796YJ_GF5284
#define HGGQMVJK_892302_NGFSLEU_796YJ_GF5284

#include <quantity/Amount.h>

template <typename Unit>
struct Quantity
{
    Quantity(const Amount amount, const Unit& unit)      
      : amountInBaseUnit(unit.toAmountInBaseUnit(amount))
    {}
    
    bool operator==(const Quantity& rhs) const
    {
        return amountInBaseUnit == rhs.amountInBaseUnit;
    }
    
    bool operator!=(const Quantity& rhs) const
    {
        return !(*this == rhs);
    }
    
private:
    const Amount amountInBaseUnit;
};

#endif
// quantity/Length.h
#ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH
#define TYIW7364_JG6389457_BVGD7562_VNW12_JFH
 
#include "quantity/Quantity.h"
#include "quantity/LengthUnit.h"

typedef Quantity<LengthUnit> Length;

#endif
// quantity/Volume.h
#ifndef HG764MD_NKGJKDSJLD_RY64930_NVHF977E
#define HG764MD_NKGJKDSJLD_RY64930_NVHF977E
 
#include "quantity/Quantity.h"
#include "quantity/VolumeUnit.h"

typedef Quantity<VolumeUnit> Volume;

#endif

如上的設(shè)計(jì),泛型類Quantity的實(shí)現(xiàn)都放在了頭文件聪蘸,不穩(wěn)定的實(shí)現(xiàn)細(xì)節(jié)宪肖,例如計(jì)算amountInBaseUnit的算法變化等因素表制,將導(dǎo)致包含LengthVolume的所有源文件都需要重新編譯。

更重要的是控乾,因?yàn)?code>LengthUnit, VolumeUnit頭文件的包含么介,如果因需求變化需要增加支持的單位,將間接導(dǎo)致了包含LengthVolume的所有源文件也需要重新編譯蜕衡。

如何控制和隔離Quantity, LengthUnit, VolumeUnit變化的蔓延壤短,而避免大部分的客戶代碼重新編譯,從而與客戶徹底解偶呢慨仿?可以通過顯式模板實(shí)例化將模板實(shí)現(xiàn)從頭文件中剝離出去久脯,從而避免了不必要的依賴。

正例:

// quantity/Quantity.h
#ifndef HGGQMVJK_892302_NGFSLEU_796YJ_GF5284
#define HGGQMVJK_892302_NGFSLEU_796YJ_GF5284

#include <quantity/Amount.h>

template <typename Unit>
struct Quantity
{
    Quantity(const Amount amount, const Unit& unit);
        
    bool operator==(const Quantity& rhs) const;
    bool operator!=(const Quantity& rhs) const;
        
private:
    const Amount amountInBaseUnit;
};

#endif
// quantity/Quantity.tcc
#ifndef FKJHJT68302_NVGKS97474_YET122_HEIW8565
#define FKJHJT68302_NVGKS97474_YET122_HEIW8565

#include <quantity/Quantity.h>

template <typename Unit>
Quantity<Unit>::Quantity(const Amount amount, const Unit& unit)      
  : amountInBaseUnit(unit.toAmountInBaseUnit(amount))
{}
    
template <typename Unit>
bool Quantity<Unit>::operator==(const Quantity& rhs) const
{
    return amountInBaseUnit == rhs.amountInBaseUnit;
}
    
template <typename Unit>
bool Quantity<Unit>::operator!=(const Quantity& rhs) const
{
    return !(*this == rhs);
}

#endif
// quantity/Length.h
#ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH
#define TYIW7364_JG6389457_BVGD7562_VNW12_JFH

#include "quantity/Quantity.h"

struct LengthUnit;
struct Length : Quantity<LengthUnit> {};

#endif
// quantity/Length.cpp
#include "quantity/Quantity.tcc"
#include "quantity/LengthUnit.h"

template struct Quantity<LengthUnit>;
// quantity/Volume.h
#ifndef HG764MD_NKGJKDSJLD_RY64930_NVHF977E
#define HG764MD_NKGJKDSJLD_RY64930_NVHF977E

#include "quantity/Quantity.h"

struct VolumeUnit;
struct Volume : Quantity<VolumeUnit> {};

#endif
// quantity/Volume.cpp
#include "quantity/Quantity.tcc"
#include "quantity/VolumeUnit.h"

template struct Quantity<VolumeUnit>;

Length.h僅僅對(duì)Quantity.h產(chǎn)生依賴; 特殊地镰吆,Length.cpp沒有產(chǎn)生對(duì)Length.h的依賴帘撰,相反對(duì)Quantity.tcc產(chǎn)生了依賴。

另外万皿,Length.h對(duì)LengthUnit的依賴關(guān)系也簡(jiǎn)化為聲明依賴摧找,而對(duì)其真正的編譯時(shí)依賴,也控制在模板實(shí)例化的時(shí)刻牢硅,即在Length.cpp內(nèi)部蹬耘。

LenghtUnit, VolumeUnit的變化,及其Quantity.tcc實(shí)現(xiàn)細(xì)節(jié)的變化唤衫,被完全地控制在Length.cpp, Volume.cpp內(nèi)部婆赠。

子類化優(yōu)于typedef/using

如果使用typedef绵脯,如果存在對(duì)Length的依賴佳励,即使是名字的聲明依賴,除了包含頭文件之外蛆挫,別無選擇赃承。

另外,如果Quantity存在virtual函數(shù)時(shí)悴侵,Length還有進(jìn)一步擴(kuò)展Quantity的可能性瞧剖,從而使設(shè)計(jì)提供了更大的靈活性。

反例:

// quantity/Length.h
#ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH
#define TYIW7364_JG6389457_BVGD7562_VNW12_JFH

#include "quantity/Quantity.h"

struct LengthUnit;
typedef Quantity<LengthUnit> Length;

#endif

正例:

// quantity/Length.h
#ifndef TYIW7364_JG6389457_BVGD7562_VNW12_JFH
#define TYIW7364_JG6389457_BVGD7562_VNW12_JFH

#include "quantity/Quantity.h"

struct LengthUnit;
struct Length : Quantity<LengthUnit> {};

#endif

附錄:實(shí)用宏

為了提高代碼的表現(xiàn)力可免,規(guī)范中使用了一部分實(shí)用的宏定義抓于。

// base/Default.h
#ifndef GKOQWPRT_1038935_NCVBNMZHJS_8909603
#define GKOQWPRT_1038935_NCVBNMZHJS_8909603

namespace details
{
   template <typename T>
   struct DefaultValue
   {
      static T value()
      {
         return T();
      }
   };

   template <typename T>
   struct DefaultValue<T*>
   {
       static T* value()
       {
           return 0;
       }
   };

   template <typename T>
   struct DefaultValue<const T*>
   {
       static T* value()
       {
           return 0;
       }
   };

   template <>
   struct DefaultValue<void>
   {
      static void value()
      {
      }
   };
}

#define DEFAULT(type, method)  \
    virtual type method { return ::details::DefaultValue<type>::value(); }

#endif

DEFAULT對(duì)于定義空實(shí)現(xiàn)的virtual函數(shù)非常方便。需要注意的是浇借,所有計(jì)算都是發(fā)生在編譯時(shí)的捉撮。

// base/Keywords.h
#ifndef H16274882_9153_4DB2_A2E2_F23D4CCB9381
#define H16274882_9153_4DB2_A2E2_F23D4CCB9381

#include "base/Config.h"
#include "base/Default.h"

#define ABSTRACT(...) virtual __VA_ARGS__ = 0

#if __SUPPORT_VIRTUAL_OVERRIDE
#   define OVERRIDE(...) virtual __VA_ARGS__ override
#else
#   define OVERRIDE(...) virtual __VA_ARGS__
#endif

#define EXTENDS(...) , ##__VA_ARGS__
#define IMPLEMENTS(...) EXTENDS(__VA_ARGS__)

#endif

Config.h提供了編譯器支持C++11特性的配置信息。ABSTRACT, OVERRIDE, EXTENDS, IMPLEMENTS等關(guān)鍵字妇垢,使得Java程序員也能看懂C++的代碼巾遭,也極大地改善了C++的表現(xiàn)力肉康。

// base/Role.h
#ifndef HF95EF112_D6C6_4DB0_8C1A_BE5A6CF8E3F1
#define HF95EF112_D6C6_4DB0_8C1A_BE5A6CF8E3F1
#include <base/Keywords.h>

namespace details
{
   template <typename T>
   struct Role
   {
      virtual ~Role() {}
   };
}

#define DEFINE_ROLE(type) struct type : ::details::Role<type>

#endif

通過DEFINE_ROLE的宏定義來實(shí)現(xiàn)對(duì)接口的定義,從而可以消除子類對(duì)虛擬析構(gòu)函數(shù)的重復(fù)實(shí)現(xiàn)灼舍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吼和,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子骑素,更是在濱河造成了極大的恐慌炫乓,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件献丑,死亡現(xiàn)場(chǎng)離奇詭異厢岂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阳距,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門塔粒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人筐摘,你說我怎么就攤上這事卒茬。” “怎么了咖熟?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵圃酵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我馍管,道長(zhǎng)郭赐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任确沸,我火速辦了婚禮捌锭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罗捎。我一直安慰自己观谦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布桨菜。 她就那樣靜靜地躺著豁状,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倒得。 梳的紋絲不亂的頭發(fā)上泻红,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音霞掺,去河邊找鬼谊路。 笑死,一個(gè)胖子當(dāng)著我的面吹牛根悼,可吹牛的內(nèi)容都是我干的凶异。 我是一名探鬼主播蜀撑,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼剩彬!你這毒婦竟也來了酷麦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤喉恋,失蹤者是張志新(化名)和其女友劉穎沃饶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轻黑,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糊肤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氓鄙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馆揉。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巫延,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布噩茄,位于F島的核電站,受9級(jí)特大地震影響复颈,放射性物質(zhì)發(fā)生泄漏绩聘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一耗啦、第九天 我趴在偏房一處隱蔽的房頂上張望凿菩。 院中可真熱鬧,春花似錦芹彬、人聲如沸蓄髓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至陡叠,卻和暖如春玩郊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枉阵。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工译红, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兴溜。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓侦厚,卻偏偏與公主長(zhǎng)得像耻陕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刨沦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容