第10課 面向對象的加強(default/delete、override/final)

1、default和delete關鍵字ios

(一)編譯器提供的「缺省函數編程

  1.類的成員函數:構造/析構函數、複製構造/複製賦值函數、移動構造/移動賦值函數ide

  2. 類的全局默認操做函數:operator new/delete、operator,、operator*、operator->、operator->*等。函數

(二)「=default」測試

  1. default:顯式指示編譯器生成該函數的默認版本,但僅用於類的特殊成員函數(含析構函數)。this

  2. 當類中自定義了構造函數後,該類將再也不是POD類型(可用is_pod檢查),但使用default可「恢復」其POD特質。spa

  3. default既能夠在類體裏定義,也能夠在類外定義code

  (三) 「=delete」對象

  1. 禁止使用某個函數。必須在函數第一次聲明的時候將其聲明爲delete。blog

  2. 不一樣於default,任何函數(含非成員函數或模板函數)均可以delete

  3. 在函數重載或模板特化中,可用delete來濾掉一些函數的形參類型,禁止編譯器作一些沒必要要的類型轉換或阻止特定的模板實例化

  4. explicit和delete混用會帶來混亂。所以在使用delete顯式刪除時,應該老是避免用explicit來修飾函數,反之亦然。

【編程實驗】default和delete關鍵字

#include <iostream>
using namespace std;

class Widget
{
private:
    int data;

public:
    Widget() = default;  //指示編譯器提供默認版本(不影響POD特質)
    Widget(int i):data(i){}
    Widget(double d) = delete; //刪除double版本
    explicit Widget(char c) = delete; //注意,這裏explicit與deltete混用,將產生一些混亂。

    Widget(const Widget&) = delete;
    Widget& operator=(const Widget&);//這裏沒使用default,本例將在類外定義

private:
    //2.3.2 特化版本
    //Widget 類中聲明瞭一個模板函數,當進行模板特化時,要求禁止參數爲 void* 的函數調用。
    //本意是按照 C++98 的「私有不實現」思路,將特例化的函數聲明爲private。但模板特化不能放在
    //類做用域中定義,它必須放在命名空間做用域中定義。見後面類外定義部分
    //template<>
    //void processPointer<void>(void*);  //編譯失敗
public:
    //2.3 delete在模板特化中的做用
    //2.3.1 泛化版本
    template<typename T>
    void proccessPointer(T* ptr){}
};

//在類外使用「=default」來指明使用默認版本
inline Widget& Widget::operator=(const Widget&) = default;

//2. delete重載函數
template<>
void Widget::proccessPointer<void>(void*) = delete; // 仍然是public, 但被delete

void Func(Widget w){}

void overloadFunc(int i) {};
void overloadFunc(char c) = delete; //顯式刪除char版本

//4. delete妙用
//4.1 禁止在堆上建立類對象!
class NoHeapAlloc
{
public:
    void* operator new(std::size_t) = delete; //注意這裏!
};

//4.2 禁止在棧上建立類對象!
class NoStackAlloc
{
public:
    NoStackAlloc()
    {
        cout <<"NoStackAlloc()" << endl;
    }
    ~NoStackAlloc() = delete; //注意這裏,將致使沒法自動析構函數。
};

int main()
{
    cout <<is_pod<Widget>::value << endl;  //0

    //1. 測試default與delete
    Widget w;  //ok,調用無參構造函數,己被聲明爲default;
    Widget w1;
    //Widget w2(w1);   //沒法編譯經過,由於Widget(const Widget&)己被delete
    //Widget w3 = w1;  //沒法編譯經過,由於Widget(const Widget&)己被delete
    Widget w4;
    w4 = w1;  //ok,operator=被指顯式定爲的默認函數

    //2. delete重載函數
    //2.1 delete成員函數
    Widget w5(3);
    //Widget w6(1.0);   //error, double版本的構造函數被delete

    Func(5);
    //Func(1.0);        //error, 1.0轉爲Widget須要調用double版本的構造函數,但該函數己delete。
    
    //2.2 delete普通函數 
    overloadFunc(4);
    //overloadFunc('a'); //編譯失敗,char版本被delete。同時'a'也就沒就機會隱式轉爲int。

    //2.3 delete特化的模板函數 
    int* pi = nullptr;
    void* pv = nullptr;
    w5.proccessPointer(pi); //ok;
    //w5.proccessPointer(pv); //error,void*版本的特化函數被delete

    //3. explicit與delete的衝突
    //Widget w6('a');    //error,char版本的構造函數被delete
    Func('a');           //編譯經過!!!因爲char版本的構造函數被聲明爲explicit,'a'將沒法再隱式轉爲Widget,但
                         //能夠隱式轉爲int,因而調用Widget(int)版本。這與Widget(char)聲明爲delete的初衷不符!!!

    //4. delete的妙用!
    //4.1 僅限在棧上建立對象
    NoHeapAlloc nh;
    //NoHeapAlloc* pnh = new NoHeapAlloc; //編譯失敗,由於operator new己被delete

    //4.2 僅限在堆上建立對象
    void* p = malloc(sizeof(NoStackAlloc));
    //NoStackAlloc nsa;  //編譯失敗!棧對象,會被自動析構,但析構函數己被delete。
    new (p) NoStackAlloc(); //placement new構造的對象(仍會調用構造函數來初始化對象),但編譯器不會爲
                            //其調用析構函數(可用於單例模式)。
    return 0;
}
/*輸出結果
0
NoStackAlloc()
*/

2、override和final關鍵字

(一)final兩個做用

  1. 做用於類時,能夠禁止該類被用做基類。

  2. 做用於虛函數時,會阻止它在派生類中被重寫(override)。

(二)override:強制重寫虛函數

  1. 重寫必須知足的條件

  (1)基類中的函數必須是虛函數。

  (2)基類和派生類中的函數名字必須徹底相同(析構函數除外)、函數形參的類型必須徹底相同。

  (3)基類和派生類的函數的常量性必須徹底相同

  (4)基類和派生類中的函數返回值和異常規格必須兼容。

  (5)基類和派生類中的函數引用飾詞必須徹底相同(詳見《知識擴展》部分)

  2. 知識擴展——左值/右值引用類型的重載函數(&和&&)

  (1)能夠利用引用飾詞(&或&&)進行函數的重載。

  (2)左值引用類型的重載函數:形如,retType& func()&。(注意,返回左值,該函數僅在*this是左值時調用)

  (3)右值引用類型的重載函數:形如,retType func()&&。(注意,返回右值,該函數僅在*this是右值時調用)。

  (4)成員函數末尾加引用飾詞(&和&&),相似末尾加const情形,後者代表只有*this爲const才能調用。

【編程實驗】override和final關鍵字

#include <iostream>
#include <vector>
#include <boost/type_index.hpp>
using namespace std;
using boost::typeindex::type_id_with_cvr;

//輔助類模板,用於打印T的類型
template <typename T>
void printType(string s)
{
    cout << s << " = " << type_id_with_cvr<T>().pretty_name() << endl;
}

//1. 重寫(override)的條件
class Base
{
public:
    virtual void f1() const {};
    virtual void f2(int x) {}
    virtual void f3() & {};
    void f4() const {};
};

//class Derived : public Base
//{
//public:
//    virtual void f1() override {};  //error,const常量性不一樣!基類爲void f1() const
//    virtual void f2(unsigned int x) override{} //error,函數形參類型不一樣(基類爲void f2(int x))
//    virtual void f3()&& override{}  //error,引用飾詞不一樣(基類爲void f3() & )
//    virtual f4() const override{}   //error,非虛函數,不能override
//};

//2. final用於類和虛函數
class MathObject  //數學類(接口類)
{
public:
    virtual double Arith() = 0; //算術運算
    virtual void Print() = 0;   //打印
};

class Printable : public MathObject  
{
public:
    void Print() final //爲了保證打印風格統一,加final以阻止在子類中被重寫
    {
        cout << "Output is : " << Arith() << endl;
    }
};

class Add final: public Printable  //在類上加final表示該類再也不被繼承
{
    double x, y;
public:
    Add(double a, double b):x(a),y(b){}
    double Arith() { return x + y; }
};

//class Add3 : public Add  //Add被聲明爲final,沒法做爲基類
//{
//};

//3. 左值 /右值引用類型的重載函數
class Widget
{
public:
    using DataType = std::vector<double>;

    //左值引用類型版本
    DataType& data() & //對於*this爲左值時,調用該函數。注意返回引用
    {
        cout <<"invoke DataType& data() & " << endl;
        return values;
    }

    //右值引用類型版本
    DataType data() && //對於*this爲右值時,調用該函數。注意返回右值
    {
        cout << "invoke DataType data() && " << endl;
        return std::move(values);
    }
private:
    DataType values;
};

//工廠函數
Widget makeWidget()
{
    return Widget();
}
int main()
{
    //左值/右值引用類型的重載函數
    Widget w;

    decltype(auto) vals1 = w.data();           //因爲w是左值,會調用DataType& data() &
    decltype(auto) vals2 = makeWidget().data();//因爲makeWidget()返回臨時對象(是個右值),會調用DataType data() &&

    printType<decltype(vals1)>("vals1");
    printType<decltype(vals2)>("vals2");

    return 0;
}
/*輸出結果
invoke DataType& data() &
invoke DataType data() &&
vals1 = class std::vector<double,class std::allocator<double> > &
vals2 = class std::vector<double,class std::allocator<double> >
*/
相關文章
相關標籤/搜索