欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊

    者 | fengbingchun

    責(zé)編 | 屠敏

    出品 | CSDN 博客

    Scott Meyers大師Effective三部曲:Effective C++、More Effective C++、Effective STL,這三本書出版已很多年,后來又出版了Effective Modern C++。

    這里是More Effective C++的筆記:

    1. 指針與引用的區(qū)別

    void printDouble(const double& rd)
    {
    std::cout<<rd; // 不需要測試rd,它肯定指向一個double值
    }

    voidprintDouble(const double* pd)
    {
    if (pd) { // 檢查是否為
    std::cout<<*pd;
    }
    }

    inttest_item_1
    {
    char* pc = 0; // 設(shè)置指針為空值
    char& rc = *pc; // 讓指針指向空值,這是非常有害的,結(jié)果將是不確定的

    //std::string& rs; // 錯誤,引用必須被初始化
    std::strings("xyzzy");
    std::string& rs = s; // 正確,rs指向s
    std::string* ps; // 未初始化的指針,合法但危險(xiǎn)

    {
    std::strings1("Nancy");
    std::strings2("Clancy");
    std::string& rs = s1; // rs引用s1
    std::string* ps = &s1; // ps指向s1
    rs = s2; // rs仍舊引用s1,但是s1的值現(xiàn)在是"Clancy"
    ps = &s2; // ps現(xiàn)在指向s2,s1沒有改變
    }

    std::vector<int> v(10);
    v[5] = 10; // 這個被賦值的目標(biāo)對象就是操作符返回的值,如果操作符
    // 返回一個指針,那么后一個語句就得這樣寫: *v[5] = 10;

    return 0;
    }

    指針與引用看上去完全不同(指針用操作符”*”和”->”,引用使用操作符”.”),但是它們似乎有相同的功能。指針和引用都是讓你間接引用其它對象。

    在任何情況下都不能使用指向空值的引用。一個引用必須總是指向某些對象。在C++里,引用應(yīng)被初始化。

    不存在指向空值的引用這個事實(shí)意味著使用引用的代碼效率比使用指針的要高。因?yàn)樵谑褂靡弥安恍枰獪y試它的合法性。

    指針與引用的另一個重要的不同是指針可以被重新賦值以指向另一個不同的對象。但是引用則總是指向在初始化時被指定的對象,以后不能改變。

    總的來說,在以下情況下你應(yīng)該使用指針,一是你考慮到存在不指向任何對象的可能(在這種情況下,你能夠設(shè)置指針為空),二是你需要能夠在不同的時刻指向不同的對象(在這種情況下,你能改變指針的指向)。如果總是指向一個對象并且一旦指向一個對象后就不會改變指向,那么你應(yīng)該使用引用。

    當(dāng)你知道你必須指向一個對象并且不想改變其指向時,或者在重載操作符并為防止不必要的語義誤解時(最普通的例子是操作符[]),你不應(yīng)該使用指針。而在除此之外的其它情況下,則應(yīng)使用指針。

    關(guān)于引用的更多介紹參考:https://blog.csdn.net/fengbingchun/article/details/69820184

    2. 盡量使用C++風(fēng)格的類型轉(zhuǎn)換

    class Widget {
    public:
    virtual voidfunc {}
    };

    class SpecialWidget : public Widget {
    public:
    virtual voidfunc {}
    };

    voidupdate(SpecialWidget* psw) {}
    voidupdateViaRef(SpecialWidget& rsw) {}

    typedef void(*FuncPtr); // FuncPtr是一個指向函數(shù)的指針
    intdoSomething { return 1; };

    inttest_item_2
    {
    int firstNumber = 1, secondNumber = 1;
    double result1 = ((double)firstNumber) / secondNumber; // C風(fēng)格
    double result2 = static_cast<double>(firstNumber) / secondNumber; // C++風(fēng)格類型轉(zhuǎn)換

    SpecialWidget sw; // sw是一個非const對象
    const SpecialWidget& csw = sw; // csw是sw的一個引用,它是一個const對象
    //update(&csw); // 錯誤,不能傳遞一個const SpecialWidget*變量給一個處理SpecialWidget*類型變量的函數(shù)
    update(const_cast<SpecialWidget*>(&csw)); // 正確,csw的const顯示地轉(zhuǎn)換掉(csw和sw兩個變量值在update函數(shù)中能被更新)
    update((SpecialWidget*)&csw); // 同上,但用了一個更難識別的C風(fēng)格的類型轉(zhuǎn)換

    Widget* pw = new SpecialWidget;
    //update(pw); // 錯誤,pw的類型是Widget*,但是update函數(shù)處理的是SpecialWidget*類型
    //update(const_cast<SpecialWidget*>(pw)); // 錯誤,const_cast僅能被用在影響constness or volatileness的地方,不能用在向繼承子類進(jìn)行類型轉(zhuǎn)換

    Widget* pw2 = ptr;
    update(dynamic_cast<SpecialWidget*>(pw2)); // 正確,傳遞給update函數(shù)一個指針是指向變量類型為SpecialWidget的pw2的指針, 如果pw2確實(shí)指向一個對象,否則傳遞過去的將是空指針

    Widget* pw3 = new SpecialWidget;
    updateViaRef(dynamic_cast<SpecialWidget&>(*pw3)); // 正確,傳遞給updateViaRef函數(shù)SpecailWidget pw3指針,如果pw3確實(shí)指向了某個對象,否則將拋出異常

    //double result3 = dynamic_cast<double>(firstNumber) / secondNumber; // 錯誤,沒有繼承關(guān)系
    const SpecialWidget sw4;
    //update(dynamic_cast<SpecialWidget*>(&sw4)); // 錯誤,dynamic_cast不能轉(zhuǎn)換掉const

    FuncPtr funcPtrArray[10]; // funcPtrArray是一個能容納10個FuncPtr指針的數(shù)組
    //funcPtrArray[0] = &doSomething; // 錯誤,類型不匹配
    funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); // 轉(zhuǎn)換函數(shù)指針的代碼是不可移植的(C++不保證所有的函數(shù)指針都被用一樣的方法表示),在一些情況下這樣的轉(zhuǎn)換會產(chǎn)生不正確的結(jié)果,所以應(yīng)該避免轉(zhuǎn)換函數(shù)指針類型

    return 0;
    }

    C++通過引進(jìn)四個新的類型轉(zhuǎn)換(cast)操作符克服了C風(fēng)格類型轉(zhuǎn)換的缺點(diǎn)(過于粗魯,能允許你在任何類型之間進(jìn)行轉(zhuǎn)換;C風(fēng)格的類型轉(zhuǎn)換在程序語句中難以識別),這四個操作符是:static_cast、const_cast、dynamic_cast、reinterpret_cast。

    static_cast在功能上基本上與C風(fēng)格的類型轉(zhuǎn)換一樣強(qiáng)大,含義也一樣。它也有功能上限制。例如,不能用static_cast像用C 風(fēng)格的類型轉(zhuǎn)換一樣把struct轉(zhuǎn)換成int類型或者把double類型轉(zhuǎn)換成指針類型,另外,static_cast不能從表達(dá)式中去除const屬性,因?yàn)榱硪粋€新的類型轉(zhuǎn)換操作符const_cast有這樣的功能。

    const_cast用于類型轉(zhuǎn)換掉表達(dá)式的const或volatileness屬性。如果你試圖使用const_cast來完成修改constness或者volatileness屬性之外的事情,你的類型轉(zhuǎn)換將被拒絕。

    dynamic_cast被用于安全地沿著類的繼承關(guān)系向下進(jìn)行類型轉(zhuǎn)換。這就是說,你能用dynamic_cast把指向基類的指針或引用轉(zhuǎn)換成指向其派生類或其兄弟類的指針或引用,而且你能知道轉(zhuǎn)換是否成功。失敗的轉(zhuǎn)換將返回空指針(當(dāng)對指針進(jìn)行類型轉(zhuǎn)換時)或者拋出異常(當(dāng)對引用進(jìn)行類型轉(zhuǎn)換時)。dynamic_cast在幫助你瀏覽繼承層次上是有限制的,它不能被用來缺乏虛函數(shù)的類型上,也不能用它來轉(zhuǎn)換掉constness。如你想在沒有繼承關(guān)系的類型中進(jìn)行轉(zhuǎn)換,你可能想到static_cast。如果是為了去除const,你總得用const_cast。

    reinterpret_cast使用這個操作符的類型轉(zhuǎn)換,其轉(zhuǎn)換結(jié)果幾乎都是執(zhí)行期定義(implementation-defined)。因此,使用reinterpret_cast的代碼很難移植。此操作符最普通的用途就是在函數(shù)指針之間進(jìn)行轉(zhuǎn)換。

    關(guān)于類型轉(zhuǎn)換更多介紹參考:https://blog.csdn.net/fengbingchun/article/details/51235498

    3. 不要對數(shù)組使用多態(tài)

    class BST {
    public:
    virtual ~BST { fprintf(stdout, "BST::~BST\n"); }
    private:
    int score;
    };

    class BalancedBST : public BST {
    public:
    virtual ~BalancedBST { fprintf(stdout, "BalancedBST::~BalancedBST\n"); }
    private:
    int length;
    int size; // 如果增加此一個int成員,執(zhí)行test_item_3會segmentation fault,注釋掉此變量,運(yùn)行正常
    };

    inttest_item_3
    {
    fprintf(stdout, "BST size: %d\n", sizeof(BST)); // 16
    fprintf(stdout, "BalancedBST size: %d\n", sizeof(BalancedBST)); // 24

    BST* p = new BalancedBST[10];
    delete p; // 如果sizeof(BST) != sizeof(BalancedBST),則會segmentation fault

    return 0;
    }

    C++允許你通過基類指針和引用來操作派生類數(shù)組。不過這根本就不是一個特性,因?yàn)檫@樣的代碼幾乎從不如你所愿地那樣運(yùn)行。數(shù)組與多態(tài)不能用在一起。值得注意的是如果你不從一個具體類(concrete classes)(例如BST)派生出另一個具體類(例如BalancedBST),那么你就不太可能犯這種使用多態(tài)性數(shù)組的錯誤。

    4. 避免無用的缺省構(gòu)造函數(shù)

    class EquipmentPiece {
    public:
    EquipmentPiece(int IDNumber) {}
    };

    inttest_item_4
    {
    //EquipmentPiece bestPieces[10]; // 錯誤,沒有正確調(diào)用EquipmentPiece構(gòu)造函數(shù)
    //EquipmentPiece* bestPieces2 = new EquipmentPiece[10]; // 錯誤,與上面的問題一樣

    int ID1 = 1, ID2 = 2;
    EquipmentPiece bestPieces3 = { EquipmentPiece(ID1), EquipmentPiece(ID2) }; // 正確,提供了構(gòu)造函數(shù)的參數(shù)

    // 利用指針數(shù)組來代替一個對象數(shù)組
    typedef EquipmentPiece* PEP; // PEP指針指向一個EquipmentPiece對象
    PEP bestPieces4[10]; // 正確,沒有調(diào)用構(gòu)造函數(shù)
    PEP* bestPieces5 = new PEP[10]; // 也正確
    // 在指針數(shù)組里的每一個指針被重新賦值,以指向一個不同的EquipmentPiece對象
    for (int i = 0; i < 10; ++i)
    bestPieces5[i] = new EquipmentPiece(ID1);

    // 為數(shù)組分配raw memory,可以避免浪費(fèi)內(nèi)存,使用placement new方法在內(nèi)存中構(gòu)造EquipmentPiece對象
    void* rawMemory = operator new(10*sizeof(EquipmentPiece));
    // make bestPieces6 point to it so it can be treated as an EquipmentPiece array
    EquipmentPiece* bestPieces6 = static_cast<EquipmentPiece*>(rawMemory);
    // construct the EquipmentPiece objects in the memory使用"placement new"
    for (int i = 0; i < 10; ++i)
    new(&bestPieces6[i]) EquipmentPiece(ID1);
    // ...
    // 以與構(gòu)造bestPieces6對象相反的順序解構(gòu)它
    for (int i = 9; i >= 0; --i)
    bestPieces6[i].~EquipmentPiece; // 如果使用普通的數(shù)組刪除方法,程序的運(yùn)行將是不可預(yù)測的
    // deallocate the raw memory
    delete rawMemory;

    return 0;
    }

    構(gòu)造函數(shù)能初始化對象,而缺省構(gòu)造函數(shù)則可以不利用任何在建立對象時的外部數(shù)據(jù)就能初始化對象。有時這樣的方法是不錯的。例如一些行為特性與數(shù)字相仿的對象被初始化為空值或不確定的值也是合理的,還有比如鏈表、哈希表、圖等等數(shù)據(jù)結(jié)構(gòu)也可以被初始化為空容器。但不是所有的對象都屬于上述類型,對于很多對象來說,不利用外部數(shù)據(jù)進(jìn)行完全的初始化是不合理的。比如一個沒有輸入姓名的地址薄對象,就沒有任何意義。

    利用指針數(shù)組代替一個對象數(shù)組這種方法有兩個缺點(diǎn):第一你必須刪除數(shù)組里每個指針?biāo)赶虻膶ο蟆H绻耍蜁l(fā)生內(nèi)存泄漏。第二增加了內(nèi)存分配量,因?yàn)檎缒阈枰臻g來容納EquipmentPiece對象一樣,你也需要空間來容納指針。

    對于類里沒有定義缺省構(gòu)造函數(shù)還會造成它們無法在許多基于模板(template-based)的容器類里使用。因?yàn)閷?shí)例化一個模板時,模板的類型參數(shù)應(yīng)該提供一個缺省構(gòu)造函數(shù)。在多數(shù)情況下,通過仔細(xì)設(shè)計(jì)模板可以杜絕對缺省構(gòu)造函數(shù)的需求。

    5. 謹(jǐn)慎定義類型轉(zhuǎn)換函數(shù)

    class Name {
    public:
    Name(const std::string& s); // 轉(zhuǎn)換string到Name
    };

    class Rational {
    public:
    Rational(int numerator = 0, int denominator = 1) // 轉(zhuǎn)換int到有理數(shù)類
    {
    n = numerator;
    d = denominator;
    }

    operatordouble const // 轉(zhuǎn)換Rational類成double類型
    {
    return static_cast<double>(n) / d;
    }

    doubleasDouble const
    {
    return static_cast<double>(n) / d;
    }

    private:
    int n, d;
    };

    template<class T>
    class Array {
    public:
    Array(int lowBound, int highBound) {}
    explicitArray(int size) {}
    T& operator(int index) { return data[index]; }

    private:
    T* data;
    };

    bool operator== (const Array<int>& lhs, const Array<int>& rhs)
    { return false; }

    inttest_item_5
    {
    Rational r(1, 2); // r的值是1/2
    double d = 0.5 * r; // 轉(zhuǎn)換r到double,然后做乘法
    fprintf(stdout, "value: %f\n", d);

    std::cout<<r<<std::endl; // 應(yīng)該打印出"1/2",但事與愿違,是一個浮點(diǎn)數(shù),而不是一個有理數(shù),隱式類型轉(zhuǎn)換的缺點(diǎn)
    // 解決方法是不使用語法關(guān)鍵字的等同的函數(shù)來替代轉(zhuǎn)換運(yùn)算符,如增加asDouble函數(shù),去掉operator double

    Array<int> a(10);
    Array<int> b(10);
    for (int i = 0; i < 10; ++i) {
    //if (a == b[i]) {} // 如果構(gòu)造函數(shù)Array(int size)沒有explicit關(guān)鍵字,編譯器將能通過調(diào)用Array<int>構(gòu)造函數(shù)能轉(zhuǎn)換int類型到Array<int>類型,這個構(gòu)造函數(shù)只有一個int類型的參數(shù),加上explicit關(guān)鍵字則可避免隱式轉(zhuǎn)換

    if (a == Array<int>(b[i])) {} // 正確,顯示從int到Array<int>轉(zhuǎn)換(但是代碼的邏輯不合理)
    if (a == static_cast<Array<int>>(b[i])) {} // 同樣正確,同樣不合理
    if (a == (Array<int>)b[i]) {} // C風(fēng)格的轉(zhuǎn)換也正確,但是邏輯依舊不合理
    }
    return 0;
    }

    C++編譯器能夠在兩種數(shù)據(jù)類型之間進(jìn)行隱式轉(zhuǎn)換(implicit conversions),它繼承了C語言的轉(zhuǎn)換方法,例如允許把char隱式轉(zhuǎn)換為int和從short隱式轉(zhuǎn)換為double。你對這些類型轉(zhuǎn)換是無能為力的,因?yàn)樗鼈兪钦Z言本身的特性。不過當(dāng)你增加自己的類型時,你就可以有更多的控制力,因?yàn)槟隳苓x擇是否提供函數(shù)讓編譯器進(jìn)行隱式類型轉(zhuǎn)換。

    有兩種函數(shù)允許編譯器進(jìn)行這些的轉(zhuǎn)換:單參數(shù)構(gòu)造函數(shù)(single-argument constructors)和隱式類型轉(zhuǎn)換運(yùn)算符。單參數(shù)構(gòu)造函數(shù)是指只用一個參數(shù)即可調(diào)用的構(gòu)造函數(shù)。該函數(shù)可以是只定義了一個參數(shù),也可以是雖定義了多個參數(shù)但第一個參數(shù)以后的所有參數(shù)都有缺省值。

    隱式類型轉(zhuǎn)換運(yùn)算符只是一個樣子奇怪的成員函數(shù):operator關(guān)鍵字,其后跟一個類型符號。你不用定義函數(shù)的返回類型,因?yàn)榉祷仡愋途褪沁@個函數(shù)的名字。

    explicit關(guān)鍵字是為了解決隱式類型轉(zhuǎn)換而特別引入的這個特性。如果構(gòu)造函數(shù)用explicit聲明,編譯器會拒絕為了隱式類型轉(zhuǎn)換而調(diào)用構(gòu)造函數(shù)。顯式類型轉(zhuǎn)換依然合法。

    6. 自增(increment)、自減(decrement)操作符前綴形式與后綴形式的區(qū)別

    class UPInt { // unlimited precision int
    public:
    // 注意:前綴與后綴形式返回值類型是不同的,前綴形式返回一個引用,后綴形式返回一個const類型
    UPInt& operator++ // ++前綴
    {
    //*this += 1; // 增加
    i += 1;
    return *this; // 取回值
    }

    const UPInt operator++(int) // ++后綴
    {
    // 注意:建立了一個顯示的臨時對象,這個臨時對象必須被構(gòu)造并在最后被析構(gòu),前綴沒有這樣的臨時對象
    UPInt oldValue = *this; // 取回值
    // 后綴應(yīng)該根據(jù)它們的前綴形式來實(shí)現(xiàn)
    ++(*this); // 增加
    return oldValue; // 返回被取回的值
    }

    UPInt& operator-- // --前綴
    {
    i -= 1;
    return *this;
    }

    const UPInt operator--(int) // --后綴
    {
    UPInt oldValue = *this;
    --(*this);
    return oldValue;
    }

    UPInt& operator+=(int a) // +=操作符,UPInt與int相運(yùn)算
    {
    i += a;
    return *this;
    }

    UPInt& operator-=(int a)
    {
    i -= a;
    return *this;
    }

    private:
    int i;
    };

    int test_item_6
    {
    UPInt i;
    ++i; // 調(diào)用i.operator++;
    i++; // 調(diào)用i.operator++(0);
    --i; // 調(diào)用i.operator--;
    i--; // 調(diào)用i.operator--(0);

    //i++++; // 注意:++后綴返回的是const UPInt

    return 0;
    }

    無論是increment或decrement的前綴還是后綴都只有一個參數(shù),為了解決這個語言問題,C++規(guī)定后綴形式有一個int類型參數(shù),當(dāng)函數(shù)被調(diào)用時,編譯器傳遞一個0作為int參數(shù)的值給該函數(shù)。

    前綴形式有時叫做”增加然后取回”,后綴形式叫做”取回然后增加”。

    當(dāng)處理用戶定義的類型時,盡可能地使用前綴increment,因?yàn)樗男瘦^高。

    7. 不要重載”&&”, “||”,或”,”

    int test_item_7
    {
    // if (expression1 && expression2)
    // 如果重載了操作符&&,對于編譯器來說,等同于下面代碼之一
    // if (expression1.operator&&(expression2)) // when operator&& is a member function
    // if (operator&&(expression1, expression2)) // when operator&& is a global function

    return 0;
    }

    與C一樣,C++使用布爾表達(dá)式短路求值法(short-circuit evaluation)。這表示一旦確定了布爾表達(dá)式的真假值,即使還有部分表達(dá)式?jīng)]有被測試,布爾表達(dá)式也停止運(yùn)算。

    C++允許根據(jù)用戶定義的類型,來定制&&和||操作符。方法是重載函數(shù)operator&&和operator||,你能在全局重載或每個類里重載。風(fēng)險(xiǎn):你以函數(shù)調(diào)用法替代了短路求值法。函數(shù)調(diào)用法與短路求值法是絕對不同的。首先當(dāng)函數(shù)被調(diào)用時,需要運(yùn)算其所有參數(shù)。第二是C++語言規(guī)范沒有定義函數(shù)參數(shù)的計(jì)算順序,所以沒有辦法知道表達(dá)式1與表達(dá)式2哪一個先計(jì)算。完全可能與具有從左參數(shù)到右參數(shù)計(jì)算順序的短路計(jì)算法相反。因此如果你重載&&或||,就沒有辦法提供給程序員他們所期望和使用的行為特性,所以不要重載&&和||。

    同樣的理由也適用于逗號操作符。逗號操作符用于組成表達(dá)式。一個包含逗號的表達(dá)式首先計(jì)算逗號左邊的表達(dá)式,然后計(jì)算逗號右邊的表達(dá)式;整個表達(dá)式的結(jié)果是逗號右邊表達(dá)式的值。如果你寫一個非成員函數(shù)operator,你不能保證左邊的表達(dá)式先于右邊的表達(dá)式計(jì)算,因?yàn)楹瘮?shù)(operator)調(diào)用時兩個表達(dá)式作為參數(shù)被傳遞出去。但是你不能控制函數(shù)參數(shù)的計(jì)算順序。所以非成員函數(shù)的方法絕對不行。成員函數(shù)operator,你也不能依靠于逗號左邊表達(dá)式先被計(jì)算的行為特性,因?yàn)榫幾g器不一定必須按此方法去計(jì)算。因此你不能重載逗號操作符,保證它的行為特性與其被料想的一樣。重載它是完全輕率的行為。

    8. 理解各種不同含義的new和delete

    class Widget8 {
    public:
    Widget8(int widget8Size) {}
    };

    void* mallocShared(size_t size)
    {
    return operator new(size);
    }

    voidfreeShared(void* memory)
    {
    operatordelete(memory);
    }

    Widget8* constructWidget8InBuffer(void* buffer, int widget8Size)
    {
    return new(buffer) Widget8(widget8Size); // new操作符的一個用法,需要使用一個額外的變量(buffer),當(dāng)new操作符隱含調(diào)用operator new函數(shù)時,把這個變量傳遞給它
    // 被調(diào)用的operator new函數(shù)除了待有強(qiáng)制的參數(shù)size_t外,還必須接受void*指針參數(shù),指向構(gòu)造對象占用的內(nèi)存空間。這個operator new就是placement new,它看上去像這樣:
    // void * operator new(size_t, void* location) { return location; }
    }

    inttest_item_8
    {
    std::string* ps = new std::string("Memory Management"); // 使用的new是new操作符(new operator)
    //void * operator new(size_t size); // 函數(shù)operator new通常聲明
    void* rawMemory = operator new(sizeof(std::string)); // 操作符operator new將返回一個指針,指向一塊足夠容納一個string類型對象的內(nèi)存
    operatordelete(rawMemory);

    delete ps; // ps->~std::string; operator delete(ps);

    void* buffer = operator new(50*sizeof(char)); // 分配足夠的內(nèi)存以容納50個char,沒有調(diào)用構(gòu)造函數(shù)
    operatordelete(buffer); // 釋放內(nèi)存,沒有調(diào)用析構(gòu)函數(shù). 這與在C中調(diào)用malloc和free等同OA

    void* sharedMemory = mallocShared(sizeof(Widget8));
    Widget8* pw = constructWidget8InBuffer(sharedMemory, 10); // placement new
    //delete pw; // 結(jié)果不確定,共享內(nèi)存來自mallocShared,而不是operator new
    pw->~Widget8; // 正確,析構(gòu)pw指向的Widget8,但是沒有釋放包含Widget8的內(nèi)存
    freeShared(pw); // 正確,釋放pw指向的共享內(nèi)存,但是沒有調(diào)用析構(gòu)函數(shù)

    return 0;
    }

    new操作符(new operator)和new操作(operator new)的區(qū)別:

    new操作符就像sizeof一樣是語言內(nèi)置的,你不能改變它的含義,它的功能總是一樣的。它要完成的功能分成兩部分。第一部分是分配足夠的內(nèi)存以便容納所需類型的對象。第二部分是它調(diào)用構(gòu)造函數(shù)初始化內(nèi)存中的對象。new操作符總是做這兩件事情,你不能以任何方式改變它的行為。你所能改變的是如何為對象分配內(nèi)存。new操作符調(diào)用一個函數(shù)來完成必須的內(nèi)存分配,你能夠重寫或重載這個函數(shù)來改變它的行為。new操作符為分配內(nèi)存所調(diào)用函數(shù)的名字是operator new。

    函數(shù)operator new通常聲明:返回值類型是void*,因?yàn)檫@個函數(shù)返回一個未經(jīng)處理(raw)的指針,未初始化的內(nèi)存。參數(shù)size_t確定分配多少內(nèi)存。你能增加額外的參數(shù)重載函數(shù)operator new,但是第一個參數(shù)類型必須是size_t。就像malloc一樣,operator new的職責(zé)只是分配內(nèi)存。它對構(gòu)造函數(shù)一無所知。把operator new返回的未經(jīng)處理的指針傳遞給一個對象是new操作符的工作。

    placement new:特殊的operator new,接受的參數(shù)除了size_t外還有其它。

    new操作符(new operator)與operator new關(guān)系:你想在堆上建立一個對象,應(yīng)該用new操作符。它既分配內(nèi)存又為對象調(diào)用構(gòu)造函數(shù)。如果你僅僅想分配內(nèi)存,就應(yīng)該調(diào)用operator new函數(shù),它不會調(diào)用構(gòu)造函數(shù)。如果你想定制自己的在堆對象被建立時的內(nèi)存分配過程,你應(yīng)該寫你自己的operator new函數(shù),然后使用new操作符,new操作符會調(diào)用你定制的operator new。如果你想在一塊已經(jīng)獲得指針的內(nèi)存里建立一個對象,應(yīng)該用placement new。

    Deletion and Memory Deallocation:為了避免內(nèi)存泄漏,每個動態(tài)內(nèi)存分配必須與一個等同相反的deallocation對應(yīng)。函數(shù)operator delete與delete操作符的關(guān)系與operator new與new操作符的關(guān)系一樣。

    如果你用placement new在內(nèi)存中建立對象,你應(yīng)該避免在該內(nèi)存中用delete操作符。因?yàn)閐elete操作符調(diào)用operator delete來釋放內(nèi)存,但是包含對象的內(nèi)存最初不是被operator nen分配的,placement new只是返回轉(zhuǎn)到給它的指針。

    Arrays:operator new、operator delete

    9. 使用析構(gòu)函數(shù)防止資源泄漏

    用一個對象存儲需要被自動釋放的資源,然后依靠對象的析構(gòu)函數(shù)來釋放資源,這種思想不只是可以運(yùn)用在指針上,還能用在其它資源的分配和釋放上。

    資源應(yīng)該被封裝在一個對象里,遵循這個規(guī)則,你通常就能夠避免在存在異常環(huán)境里發(fā)生資源泄漏,通過智能指針的方式。

    C++確保刪除空指針是安全的,所以析構(gòu)函數(shù)在刪除指針前不需要檢測這些指針是否指向了某些對象。

    10. 在構(gòu)造函數(shù)中防止資源泄漏

    C++僅僅能刪除被完全構(gòu)造的對象(fully constructed objects),只有一個對象的構(gòu)造函數(shù)完全運(yùn)行完畢,這個對象才被完全地構(gòu)造。C++拒絕為沒有完成構(gòu)造操作的對象調(diào)用析構(gòu)函數(shù)。

    在構(gòu)造函數(shù)中可以使用try catch throw捕獲所有的異常。更好的解決方法是通過智能指針的方式。

    如果你用對應(yīng)的std::unique_ptr對象替代指針成員變量,就可以防止構(gòu)造函數(shù)在存在異常時發(fā)生資源泄漏,你也不用手工在析構(gòu)函數(shù)中釋放資源,并且你還能像以前使用非const指針一樣使用const指針,給其賦值。

    std::unique_ptr的使用參考:https://blog.csdn.net/fengbingchun/article/details/52203664

    11. 禁止異常信息(exceptions)傳遞到析構(gòu)函數(shù)外

    禁止異常傳遞到析構(gòu)函數(shù)外有兩個原因:第一能夠在異常傳遞的堆棧輾轉(zhuǎn)開解(stack-unwinding)的過程中,防止terminate被調(diào)用。第二它能幫助確保析構(gòu)函數(shù)總能完成我們希望它做的所有事情。

    12. 理解”拋出一個異常”與”傳遞一個參數(shù)”或”調(diào)用一個虛函數(shù)”間的差異

    你調(diào)用函數(shù)時,程序的控制權(quán)最終還會返回到函數(shù)的調(diào)用處,但是當(dāng)你拋出一個異常時,控制權(quán)永遠(yuǎn)不會回到拋出異常的地方。

    C++規(guī)范要求被作為異常拋出的對象必須被復(fù)制。即使被拋出的對象不會被釋放,也會進(jìn)行拷貝操作。拋出異常運(yùn)行速度比參數(shù)傳遞要慢。

    當(dāng)異常對象被拷貝時,拷貝操作是由對象的拷貝構(gòu)造函數(shù)完成的。該拷貝構(gòu)造函數(shù)是對象的靜態(tài)類型(static type)所對應(yīng)類的拷貝構(gòu)造函數(shù),而不是對象的動態(tài)類型(dynamic type)對應(yīng)類的拷貝構(gòu)造函數(shù)。

    catch子句中進(jìn)行異常匹配時可以進(jìn)行兩種類型轉(zhuǎn)換:第一種是繼承類與基類間的轉(zhuǎn)換。一個用來捕獲基類的catch子句也可以處理派生類類型的異常。這種派生類與基類(inheritance_based)間的異常類型轉(zhuǎn)換可以作用于數(shù)值、引用以及指針上。第二種是允許從一個類型化指針(typed pointer)轉(zhuǎn)變成無類型指針(untyped pointer),所以帶有const void*指針的catch子句能捕獲任何類型的指針類型異常。

    catch子句匹配順序總是取決于它們在程序中出現(xiàn)的順序。因此一個派生類異常可能被處理其基類異常的catch子句捕獲,即使同時存在有能直接處理該派生類異常的catch子句,與相同的try塊相對應(yīng)。不要把處理基類異常的catch子句放在處理派生類異常的catch子句的前面。

    把一個對象傳遞給函數(shù)或一個對象調(diào)用虛擬函數(shù)與把一個對象作為異常拋出,這之間有三個主要區(qū)別:第一,異常對象在傳遞時總被進(jìn)行拷貝;當(dāng)通過傳值方式捕獲時,異常對象被拷貝了兩次。對象作為參數(shù)傳遞給函數(shù)時不一定需要被拷貝。第二,對象作為異常被拋出與作為參數(shù)傳遞給函數(shù)相比,前者類型轉(zhuǎn)換比后者要少(前者只有兩種轉(zhuǎn)換形式)。最后一點(diǎn),catch子句進(jìn)行異常類型匹配的順序是它們在源代碼中出現(xiàn)的順序,第一個類型匹配成功的catch將被用來執(zhí)行。當(dāng)一個對象調(diào)用一個虛擬函數(shù)時,被選擇的函數(shù)位于與對象類型匹配最佳的類里,即使該類不是在源代碼的最前頭。

    try catch介紹參考:https://blog.csdn.net/fengbingchun/article/details/65939258

    13. 通過引用(reference)捕獲異常

    通過指針捕獲異常不符合C++語言本身的規(guī)范。四個標(biāo)準(zhǔn)的異常----bad_alloc(當(dāng)operator new不能分配足夠的內(nèi)存時被拋出);bad_cast(當(dāng)dynamic_cast針對一個引用(reference)操作失敗時被拋出);bad_typeid(當(dāng)dynamic_cast對空指針進(jìn)行操作時被拋出);bad_exception(用于unexpected異常)----都不是指向?qū)ο蟮闹羔槪阅惚仨毻ㄟ^值或引用來捕獲它們。

    std::exception的介紹參考:https://blog.csdn.net/fengbingchun/article/details/78303734

    14. 審慎使用異常規(guī)格(exception specifications)

    如果一個函數(shù)拋出一個不在異常規(guī)格范圍里的異常,系統(tǒng)在運(yùn)行時能夠檢測出這個錯誤,然后一個特殊函數(shù)std::unexpected將被自動地調(diào)用(This function is automatically called when a function throws an exception that is not listed in its dynamic-exception-specifier.)。std::unexpected缺省的行為是調(diào)用函數(shù)std::terminate,而std::terminate缺省的行為是調(diào)用函數(shù)abort。應(yīng)避免調(diào)用std::unexpected。

    避免在帶有類型參數(shù)的模板內(nèi)使用異常規(guī)格。

    C++允許你用其它不同的異常類型替換std::unexpected異常,通過std::set_unexpected。

    15. 了解異常處理的系統(tǒng)開銷

    采用不支持異常的方法編譯的程序一般比支持異常的程序運(yùn)行速度更快所占空間也更小。

    為了減少開銷,你應(yīng)該避免使用無用的try塊。如果使用try塊,代碼的尺寸將增加并且運(yùn)行速度也會減慢。

    16. 牢記80-20準(zhǔn)則(80-20 rule)

    80-20準(zhǔn)則說的是大約20%的代碼使用了80%的程序資源;大約20%的代碼耗用了大約80%的運(yùn)行時間;大約20%的代碼使用了80%的內(nèi)存;大約20%的代碼執(zhí)行80%的磁盤訪問;80%的維護(hù)投入于大約20%的代碼上。基本的觀點(diǎn):軟件整體的性能取決于代碼組成中的一小部分。

    17. 考慮使用lazy evaluation(懶惰計(jì)算法)

    在某些情況下要求軟件進(jìn)行原來可以避免的計(jì)算,這時lazy evaluation才是有用的。

    18. 分期攤還期望的計(jì)算

    over-eager evaluation(過度熱情計(jì)算法):在要求你做某些事情以前就完成它們。隱藏在over-eager evaluation后面的思想是如果你認(rèn)為一個計(jì)算需要頻繁進(jìn)行,你就可以設(shè)計(jì)一個數(shù)據(jù)結(jié)構(gòu)高效地處理這些計(jì)算需求,這樣可以降低每次計(jì)算需求時的開銷。

    當(dāng)你必須支持某些操作而不總需要其結(jié)果時,lazy evaluation是在這種時候使用的用以提高程序效率的技術(shù)。當(dāng)你必須支持某些操作而其結(jié)果幾乎總是被需要或不止一次地需要時,over-eager是在這種時候使用的用以提高程序效率的一種技術(shù)。

    19. 理解臨時對象的來源

    size_t countChar(const std::string& str, char ch)
    {
    // 建立一個string類型的臨時對象,通過以buffer做為參數(shù)調(diào)用string的構(gòu)造函數(shù)來初始化這個臨時對象,
    // countChar的參數(shù)str被綁定在這個臨時的string對象上,當(dāng)countChar返回時,臨時對象自動釋放

    // 將countChar(const std::string& str, char ch)修改為countChar(std::string& str, char ch)則會error
    return 1;
    }

    #define MAX_STRING_LEN 64

    inttest_item_19
    {
    char buffer[MAX_STRING_LEN];
    char c;

    std::cin >> c >> std::setw(MAX_STRING_LEN) >> buffer;
    std::cout<<"There are "<<countChar(buffer, c)<<" occurrences of the character "<<c<<" in "<<buffer<<std::endl;

    return 0;
    }

    在C++中真正的臨時對象是看不見的,它們不出現(xiàn)在你的源代碼中。建立一個沒有命名的非堆(non-heap)對象會產(chǎn)生臨時對象。這種未命名的對象通常在兩種條件下產(chǎn)生:為了使函數(shù)成功調(diào)用而進(jìn)行隱式類型轉(zhuǎn)換和函數(shù)返回對象時。

    僅當(dāng)通過傳值(by value)方式傳遞對象或傳遞常量引用(reference-to-const)參數(shù)時,才會發(fā)生這些類型轉(zhuǎn)換。當(dāng)傳遞一個非常量引用(reference-to-non-const)參數(shù)對象,就不會發(fā)生。

    C++語言禁止為非常量引用(reference-to-non-const)產(chǎn)生臨時對象。

    臨時對象是有開銷的,所以你應(yīng)該盡可能地去除它們。在任何時候只要見到常量引用(reference-to-const)參數(shù),就存在建立臨時對象而綁定在參數(shù)上的可能性。在任何時候只要見到函數(shù)返回對象,就會有一個臨時對象被建立(以后被釋放)。

    20. 協(xié)助完成返回值優(yōu)化

    class Rational20 {
    public:
    Rational20(int numerator = 0, int denominator = 1) {}

    intnumerator const { return 1; }
    intdenominator const { return 2; }
    };

    const Rational20 operator*(const Rational20& lhs, const Rational20& rhs)
    {
    // 以某種方法返回對象,能讓編譯器消除臨時對象的開銷:這種技巧是返回constructor argument而不是直接返回對象
    return Rational20(lhs.numerator * rhs.numerator, lhs.denominator * rhs.denominator);
    }

    inttest_item_20
    {
    Rational20 a = 10;
    Rational20 b(1, 2);
    Rational20 c = a * b;

    return 0;
    }

    一些函數(shù)(operator*也在其中)必須要返回對象。這就是它們的運(yùn)行方法。

    C++規(guī)則允許編譯器優(yōu)化不出現(xiàn)的臨時對象(temporary objects out of existence)。

    21. 通過重載避免隱式類型轉(zhuǎn)換

    class UPInt21 { // unlimited precision integers class
    public:
    UPInt21 {}
    UPInt21(int value) {}
    };

    const UPInt21 operator+(const UPInt21& lhs, const UPInt21& rhs) // add UPInt21+UPInt21
    {
    return UPInt21(1);
    }

    const UPInt21 operator+(const UPInt21& lhs, int rhs) // add UPInt21+int
    {
    return UPInt21(1);
    }

    const UPInt21 operator+(int lhs, const UPInt21& rhs) // add int+UPInt21
    {
    return UPInt21(1);
    }

    int test_item_21
    {
    UPInt21 upi1, upi2;
    UPInt21 upi3 = upi1 + upi2; // 正確,沒有由upi1或upi2生成臨時對象
    upi3 = upi1 + 10; // 正確,沒有由upi1或10生成臨時對象
    upi3 = 10 + upi2; // 正確,沒有由10或upi2生成臨時對象

    // 注意:注釋掉上面的operator+(UPInt21&, int)和operator+(int, UPInt21&)也正確,但是會通過臨時對象把10轉(zhuǎn)換為UPInt21

    return 0;
    }

    在C++中有一條規(guī)則是每一個重載的operator必須帶有一個用戶定義類型(user-defined type)的參數(shù)。

    利用重載避免臨時對象的方法不只是用在operator函數(shù)上。

    沒有必要實(shí)現(xiàn)大量的重載函數(shù),除非你有理由確信程序使用重載函數(shù)以后其整體效率會有顯著的提高。

    22. 考慮用運(yùn)算符的賦值形式(op=)取代其單獨(dú)形式(op)

    class Rational22 {
    public:
    Rational22(int numerator = 0, int denominator = 1) {}
    Rational22& operator+=(const Rational22& rhs) { return *this; }
    Rational22& operator-=(const Rational22& rhs) { return *this; }
    };

    // operator+根據(jù)operator+=實(shí)現(xiàn)
    const Rational22 operator+(const Rational22& lhs, const Rational22& rhs)
    {
    return Rational22(lhs) += rhs;
    }

    // operator-根據(jù)operator-=實(shí)現(xiàn)
    const Rational22 operator-(const Rational22& lhs, const Rational22& rhs)
    {
    return Rational22(lhs) -= rhs;
    }

    就C++來說,operator+、operator=和operator+=之間沒有任何關(guān)系,因此如果你想讓三個operator同時存在并具有你所期望的關(guān)系,就必須自己實(shí)現(xiàn)它們。同理,operator-, *, /, 等等也一樣。

    確保operator的賦值形式(assignment version)(例如operator+=)與一個operator的單獨(dú)形式(stand-alone)(例如operator+)之間存在正常的關(guān)系,一種好方法是后者(指operator+)根據(jù)前者(指operator+=)來實(shí)現(xiàn)。

    23. 考慮變更程序庫

    不同的程序庫在效率、可擴(kuò)展性、移植性、類型安全和其它一些領(lǐng)域上蘊(yùn)含著不同的設(shè)計(jì)理念,通過變換使用給予性能更多考慮的程序庫,你有時可以大幅度地提供軟件的效率。

    24. 理解虛擬函數(shù)、多繼承、虛基類和RTTI所需的代碼

    當(dāng)調(diào)用一個虛擬函數(shù)時,被執(zhí)行的代碼必須與調(diào)用函數(shù)的對象的動態(tài)類型相一致;指向?qū)ο蟮闹羔樆蛞玫念愋褪遣恢匾摹4蠖鄶?shù)編譯器是使用virtual table和virtual table pointers,通常被分別地稱為vtbl和vptr。

    一個vtbl通常是一個函數(shù)指針數(shù)組。(一些編譯器使用鏈表來代替數(shù)組,但是基本方法是一樣的)在程序中的每個類只要聲明了虛函數(shù)或繼承了虛函數(shù),它就有自己的vtbl,并且類中vtbl的項(xiàng)目是指向虛函數(shù)實(shí)現(xiàn)體的指針。

    你必須為每個包含虛函數(shù)的類的virtual table留出空間。類的vtbl的大小與類中聲明的虛函數(shù)的數(shù)量成正比(包括從基類繼承的虛函數(shù))。每個類應(yīng)該只有一個virtual table,所以virtual table所需的空間不會太大,但是如果你有大量的類或者在每個類中有大量的虛函數(shù),你會發(fā)現(xiàn)vtbl會占用大量的地址空間。

    一些原因?qū)е卢F(xiàn)在的編譯器一般總是忽略虛函數(shù)的inline指令。

    Virtual table只實(shí)現(xiàn)了虛擬函數(shù)的一半機(jī)制,如果只有這些是沒有用的。只有用某種方法指出每個對象對應(yīng)的vtbl時,它們才能使用。這是virtual table pointer的工作,它來建立這種聯(lián)系。每個聲明了虛函數(shù)的對象都帶著它,它是一個看不見的數(shù)據(jù)成員,指向?qū)?yīng)類的virtual table。這個看不見的數(shù)據(jù)成員也稱為vptr,被編譯器加在對象里,位置只有編譯器知道。

    關(guān)于虛函數(shù)表的介紹參考:https://blog.csdn.net/fengbingchun/article/details/79592347

    虛函數(shù)是不能內(nèi)聯(lián)的。這是因?yàn)椤眱?nèi)聯(lián)”是指”在編譯期間用被調(diào)用的函數(shù)體本身來代替函數(shù)調(diào)用的指令”,但是虛函數(shù)的”虛”是指”直到運(yùn)行時才能知道要調(diào)用的是哪一個函數(shù)”。

    RTTI(運(yùn)行時類型識別)能讓我們在運(yùn)行時找到對象和類的有關(guān)信息,所以肯定有某個地方存儲了這些信息讓我們查詢。這些信息被存儲在類型為type_info的對象里,你能通過使用typeid操作符訪問一個類的type_info對象。

    關(guān)于typeid的使用參考:https://blog.csdn.net/fengbingchun/article/details/51866559

    RTTI被設(shè)計(jì)為在類的vtbl基礎(chǔ)上實(shí)現(xiàn)。

    25. 將構(gòu)造函數(shù)和非成員函數(shù)虛擬化

    虛擬構(gòu)造函數(shù)是指能夠根據(jù)輸入給它的數(shù)據(jù)的不同而建立不同類型的對象。虛擬拷貝構(gòu)造函數(shù)能返回一個指針,指向調(diào)用該函數(shù)的對象的新拷貝。類的虛擬拷貝構(gòu)造函數(shù)只是調(diào)用它們真正的拷貝構(gòu)造函數(shù)。被派生類重定義的虛擬函數(shù)不用必須與基類的虛擬函數(shù)具有一樣的返回類型。如果函數(shù)的返回類型是一個指向基類的指針(或一個引用),那么派生類的函數(shù)可以返回一個指向基類的派生類的指針(或引用)。

    26. 限制某個類所能產(chǎn)生的對象數(shù)量

    阻止建立某個類的對象,最容易的方法就是把該類的構(gòu)造函數(shù)聲明在類的private域。

    27. 要求或禁止在堆中產(chǎn)生對象

    // 判斷一個對象是否在堆中, HeapTracked不能用于內(nèi)建類型,因?yàn)閮?nèi)建類型沒有this指針
    typedef const void* RawAddress;
    class HeapTracked { // 混合類,跟蹤
    public:
    class MissingAddress {}; // 從operator new返回的ptr異常類
    virtual ~HeapTracked = 0;
    static void* operator new(size_t size);
    static void operatordelete(void* ptr);
    boolisOnHeap const;

    private:
    static std::list<RawAddress> addresses;
    };

    std::list<RawAddress> HeapTracked::addresses;

    HeapTracked::~HeapTracked {}

    void* HeapTracked::operatornew(size_t size)
    {
    void* memPtr = ::operator new(size);
    addresses.push_front(memPtr);
    return memPtr;
    }

    void HeapTracked::operatordelete(void* ptr)
    {
    std::list<RawAddress>::iterator it = std::find(addresses.begin, addresses.end, ptr);
    if (it != addresses.end) {
    addresses.erase(it);
    ::operatordelete(ptr);
    } else {
    throw MissingAddress; // ptr就不是用operator new分配的,所以拋出一個異常
    }
    }

    bool HeapTracked::isOnHeap const
    {
    // 生成的指針將指向"原指針指向?qū)ο髢?nèi)存"的開始處
    // 如果HeapTracked::operator new為當(dāng)前對象分配內(nèi)存,這個指針就是HeapTracked::operator new返回的指針
    const void* rawAddress = dynamic_cast<const void*>(this);
    std::list<RawAddress>::iterator it = std::find(addresses.begin, addresses.end, rawAddress);
    return it != addresses.end;
    }

    class Asset : public HeapTracked {};

    // 禁止堆對象
    class UPNumber27 {
    private:
    static void* operator new(size_t size);
    static void operatordelete(void* ptr);
    };

    void* UPNumber27::operatornew(size_t size)
    {
    return ::operator new(size);
    }

    void UPNumber27::operatordelete(void* ptr)
    {
    ::operatordelete(ptr);
    }

    class Asset27 {
    public:
    Asset27(int initValue) {}

    private:
    UPNumber27 value;
    };

    inttest_item_27
    {
    UPNumber27 n1; // okay
    static UPNumber27 n2; // also okay
    //UPNumber27* p = new UPNumber27; // error, attempt to call private operator new

    // UPNumber27的operator new是private這一點(diǎn) 不會對包含UPNumber27成員對象的對象的分配產(chǎn)生任何影響
    Asset27* pa = new Asset27(100); // 正確,調(diào)用Asset::operator new或::operator new,不是UPNumber27::operator new

    return 0;
    }

    禁止堆對象:禁止用于調(diào)用new,利用new操作符總是調(diào)用operator new函數(shù)這點(diǎn)來達(dá)到目的,可以自己聲明這個函數(shù),而且你可以把它聲明為private。

    28. 靈巧(smart)指針

    // 大多數(shù)靈巧指針模板
    template<class T>
    class SmartPtr {
    public:
    SmartPtr(T* realPtr = 0); // 建立一個靈巧指針指向dumb pointer(內(nèi)建指針)所指的對象,未初始化的指針,缺省值為0
    SmartPtr(const SmartPtr& rhs); // 拷貝一個靈巧指針
    ~SmartPtr; // 釋放靈巧指針
    // make an assignment to a smart ptr
    SmartPtr& operator=(const SmartPtr& rhs);
    T* operator-> const; // dereference一個靈巧指針以訪問所指對象的成員
    T& operator* const; // dereference靈巧指針

    private:
    T* pointee; // 靈巧指針?biāo)傅膶ο?br>};

    靈巧指針是一種外觀和行為都被設(shè)計(jì)成與內(nèi)建指針相類似的對象,不過它能提供更多的功能。它們有許多應(yīng)用的領(lǐng)域,包括資源管理和重復(fù)代碼任務(wù)的自動化。

    在C++11中auto_ptr已經(jīng)被廢棄,用unique_ptr替代。

    std::unique_ptr的使用參考:https://blog.csdn.net/fengbingchun/article/details/52203664

    29. 引用計(jì)數(shù)

    class String {
    public:
    String(const char* initValue = "");
    String(const String& rhs);
    String& operator=(const String& rhs);
    const char& operator(int index) const; // for const String
    char& operator(int index); // for non-const String
    ~String;

    private:
    // StringValue的主要目的是提供一個空間將一個特別的值和共享此值的對象的數(shù)目聯(lián)系起來
    struct StringValue { // holds a reference count and a string value
    int refCount;
    char* data;
    bool shareable; // 標(biāo)志,以指出它是否為可共享的
    StringValue(const char* initValue);
    ~StringValue;
    };

    StringValue* value; // value of this String
    };

    String::String(const char* initValue) : value(new StringValue(initValue))
    {}

    String::String(const String& rhs)
    {
    if (rhs.value->shareable) {
    value = rhs.value;
    ++value->refCount;
    } else {
    value = new StringValue(rhs.value->data);
    }
    }

    String& String::operator=(const String& rhs)
    {
    if (value == rhs.value) { // do nothing if the values are already the same
    return *this;
    }

    if (value->shareable && --value->refCount == 0) { // destroy *this's value if no one else is using it
    delete value;
    }

    if (rhs.value->shareable) {
    value = rhs.value; // have *this share rhs's value
    ++value->refCount;
    } else {
    value = new StringValue(rhs.value->data);
    }

    return *this;
    }

    const char& String::operator(int index) const
    {
    return value->data[index];
    }

    char& String::operator(int index)
    {
    // if we're sharing a value with other String objects, break off a separate copy of the value fro ourselves
    if (value->refCount > 1) {
    --value->refCount; // decrement current value's refCount, becuase we won't be using that value any more
    value = new StringValue(value->data); // make a copy of the value for ourselves
    }

    value->shareable = false;
    // return a reference to a character inside our unshared StringValue object
    return value->data[index];
    }

    String::~String
    {
    if (--value->refCount == 0) {
    delete value;
    }
    }

    String::StringValue::StringValue(const char* initValue) : refCount(1), shareable(true)
    {
    data = new char[strlen(initValue) + 1];
    strcpy(data, initValue);
    }

    String::StringValue::~StringValue
    {
    delete data;
    }

    // 基類,任何需要引用計(jì)數(shù)的類都必須從它繼承
    class RCObject {
    public:
    voidaddReference { ++refCount; }
    voidremoveReference { if (--refCount == 0) delete this; } // 必須確保RCObject只能被構(gòu)建在堆中
    voidmarkUnshareable { shareable = false; }
    boolisShareable const { return shareable; }
    boolisShared const { return refCount > 1; }

    protected:
    RCObject : refCount(0), shareable(true) {}
    RCObject(const RCObject& rhs) : refCount(0), shareable(true) {}
    RCObject& operator=(const RCObject& rhs) { return *this; }
    virtual ~RCObject = 0;

    private:
    int refCount;
    bool shareable;

    };

    RCObject::~RCObject {} // virtual dtors must always be implemented, even if they are pure virtual and do nothing

    // template class for smart pointers-to-T objects. T must support the RCObject interface, typically by inheriting from RCObject
    template<class T>
    class RCPtr {
    public:
    RCPtr(T* realPtr = 0) : pointee(realPtr) { init; }
    RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init; }
    ~RCPtr { if (pointee) pointee->removeReference; }

    RCPtr& operator=(const RCPtr& rhs)
    {
    if (pointee != rhs.pointee) { // skip assignments where the value doesn't change
    if (pointee)
    pointee->removeReference; // remove reference to current value

    pointee = rhs.pointee; // point to new value
    init; // if possible, share it else make own copy
    }

    return *this;
    }

    T* operator-> const { return pointee; }
    T& operator* const { return *pointee; }

    private:
    T* pointee; // dumb pointer this object is emulating

    voidinit // common initialization
    {
    if (pointee == 0) // if the dumb pointer is , so is the smart one
    return;

    if (pointee->isShareable == false) // if the value isn't shareable copy it
    pointee = new T(*pointee);

    pointee->addReference; // note that there is now a new reference to the value
    }
    };

    // 將StringValue修改為是從RCObject繼承
    // 將引用計(jì)數(shù)功能移入一個新類(RCObject),增加了靈巧指針(RCPtr)來自動處理引用計(jì)數(shù)
    class String2 {
    public:
    String2(const char* value = "") : value(new StringValue(value)) {}
    const char& operator(int index) const { return value->data[index]; } // for const String2

    char& operator(int index) // for non-const String2
    {
    if (value->isShared)
    value = new StringValue(value->data);
    value->markUnshareable;
    return value->data[index];
    }

    private:
    // StringValue的主要目的是提供一個空間將一個特別的值和共享此值的對象的數(shù)目聯(lián)系起來
    struct StringValue : public RCObject { // holds a reference count and a string value
    char* data;

    StringValue(const char* initValue) { init(initValue); }
    StringValue(const StringValue& rhs) { init(rhs.data); }

    voidinit(const char* initValue)
    {
    data = new char[strlen(initValue) + 1];
    strcpy(data, initValue);
    }

    ~StringValue { delete data; }
    };

    RCPtr<StringValue> value; // value of this String2

    };

    inttest_item_29
    {
    String s1("More Effective C++");
    String s2 = s1;
    s1 = s2;
    fprintf(stdout, "char: %c\n", s1[2]);
    String s3 = s1;
    s3[5] = 'x';

    return 0;
    }

    引用計(jì)數(shù)是這樣一個技巧,它允許多個有相同值的對象共享這個值的實(shí)現(xiàn)。這個技巧有兩個常用動機(jī)。第一個是簡化跟蹤堆中的對象的過程。一旦一個對象通過調(diào)用new被分配出來,最要緊的就是記錄誰擁有這個對象,因?yàn)槠渌姓?---并且只有其所有者----負(fù)責(zé)對這個對象調(diào)用delete。但是,所有權(quán)可以被從一個對象傳遞到另外一個對象(例如通過傳遞指針型參數(shù))。引用計(jì)數(shù)可以免除跟蹤對象所有權(quán)的擔(dān)子,因?yàn)楫?dāng)使用引用計(jì)數(shù)后,對象自己擁有自己。當(dāng)沒人再使用它時,它自己自動銷毀自己。因此,引用計(jì)數(shù)是個簡單的垃圾回收體系。第二個動機(jī)是由于一個簡單的常識。如果很多對象有相同的值,將這個值存儲多次是很無聊的。更好的辦法是讓所有的對象共享這個值的實(shí)現(xiàn)。這么做不但節(jié)省內(nèi)存,而且可以使得程序運(yùn)行更快,因?yàn)椴恍枰獦?gòu)造和析構(gòu)這個值的拷貝。

    引用計(jì)數(shù)介紹參考:https://blog.csdn.net/fengbingchun/article/details/85861776

    實(shí)現(xiàn)引用計(jì)數(shù)不是沒有代價(jià)的。每個被引用的值帶一個引用計(jì)數(shù),其大部分操作都需要以某種形式檢查或操作引用計(jì)數(shù)。對象的值需要更多的內(nèi)存,而我們在處理它們時需要執(zhí)行更多的代碼。引用計(jì)數(shù)是基于對象通常共享相同的值的假設(shè)的優(yōu)化技巧。如果假設(shè)不成立的話,引用計(jì)數(shù)將比通常的方法使用更多的內(nèi)存和執(zhí)行更多的代碼。另一方面,如果你的對象確實(shí)有具有相同值的趨勢,那么引用計(jì)數(shù)將同時節(jié)省時間和空間。

    30. 代理類

    template<class T>
    class Array2D { // 使用代理實(shí)現(xiàn)二維數(shù)組
    public:
    Array2D(int i, int j) : i(i), j(j)
    {
    data.reset(new T[i*j]);
    }

    class Array1D { // Array1D是一個代理類,它的實(shí)例扮演的是一個在概念上不存在的一維數(shù)組
    public:
    Array1D(T* data) : data(data) {}
    T& operator(int index) { return data[index]; }
    const T& operator(int index) const { return data[index]; }

    private:
    T* data;
    };

    Array1D operator(int index) { return Array1D(data.get+j*index); }
    const Array1D operator(int index) const { return Array1D(data.get+j*index); }

    private:
    std::unique_ptr<T> data;
    int i, j;
    };

    // 可以通過代理類幫助區(qū)分通過operator進(jìn)行的是讀操作還是寫操作
    class String30 {
    public:
    String30(const char* value = "") : value(new StringValue(value)) {}

    class CharProxy { // proxies for string chars
    public:
    CharProxy(String30& str, int index) : theString(str), charIndex(index) {}

    CharProxy& operator=(const CharProxy& rhs)
    {
    // if the string is haring a value with other String objects,
    // break off a separate copy of the value for this string only
    if (theString.value->isShared)
    theString.value = new StringValue(theString.value->data);

    // now make the assignment: assign the value of the char
    // represented by rhs to the char represented by *this
    theString.value->data[charIndex] = rhs.theString.value->data[rhs.charIndex];
    return *this;
    }

    CharProxy& operator=(char c)
    {
    if (theString.value->isShared)
    theString.value = new StringValue(theString.value->data);
    theString.value->data[charIndex] = c;
    return *this;
    }

    operator char const { return theString.value->data[charIndex]; }

    private:
    String30& theString;
    int charIndex;
    };

    const CharProxy operator(int index) const // for const String30
    {
    return CharProxy(const_cast<String30&>(*this), index);
    }

    CharProxy operator(int index) // for non-const String30
    {
    return CharProxy(*this, index);
    }

    //friend class CharProxy;
    private:
    // StringValue的主要目的是提供一個空間將一個特別的值和共享此值的對象的數(shù)目聯(lián)系起來
    struct StringValue : public RCObject { // holds a reference count and a string value
    char* data;

    StringValue(const char* initValue) { init(initValue); }
    StringValue(const StringValue& rhs) { init(rhs.data); }

    void init(const char* initValue)
    {
    data = new char[strlen(initValue) + 1];
    strcpy(data, initValue);
    }

    ~StringValue { delete data; }
    };

    RCPtr<StringValue> value; // value of this String30

    };

    int test_item_30
    {
    Array2D<float> data(10, 20);
    fprintf(stdout, "%f\n", data[3][6]);

    String30 s1("Effective C++"), s2("More Effective C++"); // reference-counted strings using proxies
    fprintf(stdout, "%c\n", s1[5]); // still legal, still works
    s2[5] = 'x'; // also legal, also works
    s1[3] = s2[8]; // of course it's legal, of course it works

    //char* p = &s1[1]; // error, 通常,取proxy對象地址的操作與取實(shí)際對象地址的操作得到的指針,其類型是不同的,重載CharProxy類的取地址運(yùn)算可消除這個不同

    return 0;
    }

    可以通過代理類實(shí)現(xiàn)二維數(shù)組。

    可以通過代理類幫助區(qū)分通過operator進(jìn)行的是讀操作還是寫操作。

    Proxy類可以完成一些其它方法很難甚至可不能實(shí)現(xiàn)的行為。多維數(shù)組是一個例子,左值/右值的區(qū)分是第二個,限制隱式類型轉(zhuǎn)換是第三個。

    同時,proxy類也有缺點(diǎn)。作為函數(shù)返回值,proxy對象是臨時對象,它們必須被構(gòu)造和析構(gòu)。Proxy對象的存在增加了軟件的復(fù)雜度。從一個處理實(shí)際對象的類改換到處理proxy對象的類經(jīng)常改變了類的語義,因?yàn)閜roxy對象通常表現(xiàn)出的行為與實(shí)際對象有些微妙的區(qū)別。

    31. 讓函數(shù)根據(jù)一個以上的對象來決定怎么虛擬

    32. 在未來時態(tài)下開發(fā)程序

    未來時態(tài)的考慮增加了你的代碼的可重用性、可維護(hù)性、健壯性,以及在環(huán)境發(fā)生改變時易于修改。

    33. 將非尾端類設(shè)計(jì)為抽象類

    34. 如何在同一程序中混合使用C++和C

    名變換:就是C++編譯器給程序的每個函數(shù)換一個獨(dú)一無二的名字。在C中,這個過程是不需要的,因?yàn)闆]有函數(shù)重載,但幾乎所有C++程序都有函數(shù)重名。要禁止名變換,使用C++的extern “C”。不要將extern “C”看作是聲明這個函數(shù)是用C語言寫的,應(yīng)該看作是聲明這個函數(shù)應(yīng)該被當(dāng)作好像C寫的一樣而進(jìn)行調(diào)用。

    靜態(tài)初始化:在main執(zhí)行前和執(zhí)行后都有大量代碼被執(zhí)行。尤其是,靜態(tài)的類對象和定義在全局的、命名空間中的或文件體中的類對象的構(gòu)造函數(shù)通常在main被執(zhí)行前就被調(diào)用。這個過程稱為靜態(tài)初始化。同樣,通過靜態(tài)初始化產(chǎn)生的對象也要在靜態(tài)析構(gòu)過程中調(diào)用其析構(gòu)函數(shù),這個過程通常發(fā)生在main結(jié)束運(yùn)行之后。

    動態(tài)內(nèi)存分配:C++部分使用new和delete,C部分使用malloc(或其變形)和free。

    數(shù)據(jù)結(jié)構(gòu)的兼容性:在C++和C之間這樣相互傳遞數(shù)據(jù)結(jié)構(gòu)是安全的----在C++和C下提供同樣的定義來進(jìn)行編譯。在C++版本中增加非虛成員函數(shù)或許不影響兼容性,但幾乎其它的改變都將影響兼容。

    如果想在同一程序下混合C++與C編程,記住下面的指導(dǎo)原則:(1).確保C++和C編譯器產(chǎn)生兼容的obj文件;(2).將在兩種語言下都使用的函數(shù)聲明為extern “C”;(3).只要可能,用C++寫main;(4).總用delete釋放new分配的內(nèi)存;總用free釋放malloc分配的內(nèi)存;(5).將在兩種語言間傳遞的東西限制在用C編譯的數(shù)據(jù)結(jié)構(gòu)的范圍內(nèi);這些結(jié)構(gòu)的C++版本可以包含非虛成員函數(shù)。

    35. 讓自己習(xí)慣使用標(biāo)準(zhǔn)C++語言

    GitHub:https://github.com/fengbingchun/Messy_Test

    版權(quán)聲明:本文為CSDN博主「fengbingchun」的原創(chuàng)文章,版權(quán)歸作者所有。

    技術(shù)的道路一個人走著極為艱難?

    一身的本領(lǐng)得不施展?

    優(yōu)質(zhì)的文章得不到曝光?

    別擔(dān)心,

    即刻起,CSDN 將為你帶來創(chuàng)新創(chuàng)造創(chuàng)變展現(xiàn)的大舞臺,

    掃描下方二維碼,歡迎加入 CSDN 「原力計(jì)劃」!

    【End】

    更多深度文章,請關(guān)注云計(jì)算頻道:https://yq.aliyun.com/cloud

    動態(tài)表的概念是社區(qū)很早就提出的但并沒有全部實(shí)現(xiàn)下文中所有介紹都是基于已有規(guī)劃和proposal給出的,可能與之后實(shí)現(xiàn)存在出入僅供參考

    概念

    動態(tài)表直觀上看是一個類似于數(shù)據(jù)庫中的Materialized View概念。動態(tài)表隨著時間改變;類似靜態(tài)的batch table一樣可以用標(biāo)準(zhǔn)SQL進(jìn)行查詢?nèi)缓笠粋€新的動態(tài)表;可以和流無損地互相轉(zhuǎn)換(對偶的)。對現(xiàn)有的API最大的改進(jìn)關(guān)鍵在表的內(nèi)容隨著時間改變,而現(xiàn)在的狀態(tài)只是append。當(dāng)前的streaming table可以認(rèn)為是一種動態(tài)表,append模式的動態(tài)表。

    流到 Dynamic Table

    流被轉(zhuǎn)換成Table時決定選擇哪種模式是依據(jù)表的schema是否定義primary key。

    Append模式:

    如果表的schema沒有包括key的定義那轉(zhuǎn)換成表時采用append模式。把流中每條新來的record當(dāng)做新的row append到表中。一旦數(shù)據(jù)加到表中就不能再被更新和刪除(指當(dāng)前表中,不考慮轉(zhuǎn)換成新表)。

    Replace模式:

    相對應(yīng),如果定義了key,那么對于流中的每條記錄如果key不在表中就insert否則就update。

    Dynamic Table 到 流

    表到流的操作是把表的所有change以changelog stream的方式發(fā)送到下游。這一步也有兩種模式。

    Retraction模式:

    traction模式中對于Dynamic Table的insert和delete的change分別產(chǎn)生insert或delete event。如果是update的change會產(chǎn)生兩種change event,對于之前發(fā)送出去的同樣key的record會產(chǎn)生delete event,對于當(dāng)前的record是產(chǎn)生insert event。如下圖所示:

    Update模式:

    update模式依賴Dynamic Table定義了key。所有的change event是一個kv對。key對應(yīng)表的key在當(dāng)前record中的值;對于insert和change value對應(yīng)新的record。對于delete value是空表示該可以已經(jīng)被刪除。如下圖所示:

    example

    表的內(nèi)容隨著時間改變意味著對表的query結(jié)果也是隨著時間改變的。我們定義:

    • A[t]: 時間t時的表A

    • q(A[t]):時間t時對表A執(zhí)行query q

    舉個例子來理解動態(tài)表的概念:

    query的限制

    由于流是無限的,相對應(yīng) Dynamic Table 也是無界的。當(dāng)查詢無限的表的時候我們需要保證query的定時是良好的,有意義可行的。

    1.在實(shí)踐中Flink將查詢轉(zhuǎn)換成持續(xù)的流式應(yīng)用,執(zhí)行的query僅針對當(dāng)前的邏輯時間,所以不支持對于任意時間點(diǎn)的查詢(A[t])。

    2.最直觀的原則是query可能的狀態(tài)和計(jì)算必須是有界的,所以可以支持可增量計(jì)算的查詢:

    • 不斷更新當(dāng)前結(jié)果的查詢:查詢可以產(chǎn)生insert,update和delete更改。查詢可以表示為 Q(t+1) = q'(Q(t), c(T, t, t+1)),其中Q(t)是query q的前一次查詢結(jié)果,c(T, t, t_+1) 是表T從t+1到t的變化, q'是q的增量版本。

    • 產(chǎn)生append-only的表,可以從輸入表的尾端直接計(jì)算出新數(shù)據(jù)。查詢可以表示為 Q(t+1) = q''(c(T, t-x, t+1)) ∪ Q(t),q''是不需要時間t時q的結(jié)果增量版本query q。c(T, t-x, t+1)是表T尾部的x+1個數(shù)據(jù),x取決于語義。例如最后一小時的window aggregation至少需要最后一小時的數(shù)據(jù)作為狀態(tài)。其他能支持的查詢類型還有:單獨(dú)在每一行上操作的SELECT WHERE;rowtime上的GROUP BY子句(比如基于時間的window aggregate);ORDER BY rowtime的OVER windows(row-windows);ORDER BY rowtime。 3.當(dāng)輸入表足夠小時,對表的每條數(shù)據(jù)進(jìn)行訪問。比如對兩個大小固定的流表(比如key的個數(shù)固定)進(jìn)行join。

    中間狀態(tài)有界

    如上文所說的,某些增量查詢需要保留一些數(shù)據(jù)(部分輸入數(shù)據(jù)或者中間結(jié)果)作為狀態(tài)。為了保證query不會失敗,保證查詢所需要的空間是有界的不隨著時間無限增長很重要。主要有兩個原因使得狀態(tài)增長:

    1. 不受時間謂詞約束的中間計(jì)算狀態(tài)的增長(比如 聚合key的膨脹)

    2. 時間有界但是需要遲到的數(shù)據(jù)(比如 window 的聚合)

    雖然第二種情況可有通過下文提到的"Last Result Offset"參數(shù)解決,但是第一種情況需要優(yōu)化器檢測。我們應(yīng)該拒絕不受時間限制的中間狀態(tài)增長的查詢。優(yōu)化器應(yīng)該提供如何修復(fù)查詢且要求有適當(dāng)?shù)臅r間謂詞。比如下面這個查詢:

    SELECT user, page, COUNT(page) AS pCntFROM pageviewsGROUP BY user, page

    隨著用戶數(shù)和頁面數(shù)的增長,中間狀態(tài)會數(shù)據(jù)隨著時間推移而增長。對于存儲空間的要求可以通過添加時間謂詞來限制:

    SELECT user, page, COUNT(page) AS pCntFROM pageviewsWHERE rowtime BETWEEN now() - INTERVAL '1' HOUR AND now() // only last hourGROUP BY user, page

    因?yàn)椴皇撬袑傩远际遣粩嘣鲩L的, 因此可以告訴優(yōu)化器domain的size, 就可以推斷中間狀態(tài)不會隨著時間推移而增長,然后接受沒有時間謂詞的查詢。

    val sensorT: Table = sensors

    結(jié)果的計(jì)算和細(xì)化時序

    一些關(guān)系運(yùn)算符必須等數(shù)據(jù)到達(dá)才能計(jì)算最終結(jié)果。例如:在10:30關(guān)閉的窗口至少要等到10:30才能計(jì)算出最終的結(jié)果。Flink的logical clock(即 決定何時才是10:30)取決于使用event time 還是 processing time。在processing time的情況下,logical time是每個機(jī)器的wallclock;在event time的情況下,logical clock time是由源頭提供的watermark決定的。由于數(shù)據(jù)的亂序和延遲當(dāng)在event time模式下時等待一段時間來減小計(jì)算結(jié)果不完整性。另一方面某些情況下希望得到不斷改進(jìn)的早期結(jié)果。因此對于結(jié)果被計(jì)算、改進(jìn)或者做出最終結(jié)果時有不同的要求、

    下圖描繪了不同的配置參數(shù)如何用于控制早期結(jié)果和細(xì)化計(jì)算結(jié)果的。

    • "First Result Offset" 指第一個早期結(jié)果被計(jì)算的結(jié)果的時間。時間是相對于第一次可以計(jì)算完整結(jié)果的時間(比如相對于window的結(jié)束時間10:30)。如果設(shè)置的是-10分鐘,對于結(jié)束時間是10:30的window那么第一個被發(fā)出去的結(jié)果是在邏輯時間10:20計(jì)算的。這個參數(shù)的默認(rèn)值是0,即在window結(jié)束的時候才計(jì)算結(jié)果。

    • "Complete Result Offset" 指完整的結(jié)果被計(jì)算的時間。時間是相對于第一次可以計(jì)算完整的時間。如果設(shè)置的是+5分鐘,對于結(jié)束時間是10:30的window那么產(chǎn)生完整結(jié)果的時間是10:35。這個參數(shù)可以減輕延遲數(shù)據(jù)造成的影響。默認(rèn)是0,即在window結(jié)束的時候計(jì)算的結(jié)果就是完整結(jié)果。

    • "Update Rate" 指計(jì)算完整結(jié)果之前一次次更新結(jié)果的時間間隔(可以是時間和次數(shù))。如果設(shè)為5分鐘,窗口大小是30分鐘的tumbling window,開始時間是10:300,"First Result Offset"是-15分鐘, "Complete Result Offset"是2分鐘,那么將在10:20, 10:25, 10:30更新結(jié)果,10:15禪城寄一個結(jié)果,10:32產(chǎn)生完整結(jié)果。

    • "Last Updates Switch" 指完整結(jié)果發(fā)出后對于延遲的數(shù)據(jù)是否計(jì)算延遲更新,直到計(jì)算狀態(tài)被清除。

    • "Last Result Offset" 指可計(jì)算的最后一個結(jié)果的時間。這是內(nèi)部狀態(tài)被清除的時間,清除狀態(tài)后再到達(dá)的數(shù)據(jù)將被丟棄。Last Result Offset 意味著計(jì)算的結(jié)果是近似值,不能保證精確。

網(wǎng)站首頁   |    關(guān)于我們   |    公司新聞   |    產(chǎn)品方案   |    用戶案例   |    售后服務(wù)   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區(qū)    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權(quán)所有