2017年4月7日 星期五

C++ 副程式的 const左右值引用 的完美轉發

C++ 副程式的 const左右值引用 的完美轉發

tags: C++ Concept2

四種不同屬性的函式

已知存在著四種屬性
  • lvalue
  • lvalue const
  • rvalue
  • rvalue const
他們分別呼叫的副程式參數長這個樣子
void fun(int & i){
    cout << "fun L" << endl;
}
void fun(int const & i){
    cout << "fun L const" << endl;
}
void fun(int && i){
    cout << "fun R" << endl;
}
void fun(int const && i){
    cout << "fun R const" << endl;
}
如此宣告我們可以統一使用 fun() 這個名稱呼叫四種不同的函式,分別是這樣呼叫
    int a=0;
    int const b=0;

    fun(a);
    fun(b);
    fun(move(a));
    fun(move(b));

函式轉發

如果你想要用另一個副程式來呼叫這個 fun() 藉此組合更多的功能可能會遇到一些問題
  • const 與 non-const區分
  • 左值與右值區分
  • 右值進來有了名字變成左值

const 與非 const

可以試著重載const藉此可以同時引入兩種版本
template<class T>
void tran(T const & t){
    fun(t);
}
不過如此一來將導致你沒有辦法修改內容,也無從判斷原本是否是const
可以透過&&達到正確的結果
template<class T>
void tran(T && t){
    fun(t);
}
如此由 template 解決引數是否有 const,由 && 解決引數是左值還是右值。

消除名字

不過仍然有一個問題,參數本身是帶有名字的再引入的時候有了名字就變成左值
可以透過 forward<T>(t) 消除名字,獲取原本的屬性,
該是右值就還原右值,該是左值則保持左值
template<class T>
void tran(T && t){
    fun(forward<T>(t));
}
如此一來我們就可以好像換了一個別名似的,用第二個名字自由的操作,可以在函式內加上一些功能,增加代碼的重複性。
template<class T>
void tran(T && t){
    // 其他功能
    fun(forward<T>(t));
}

fun();
tran();
需要 fun() 時使用他,需要fun() 加上一額外的功能使用 tran() ,並可將重複的代寫在一起

為什麼不是使用 move() 語意即可?

確實可以透過move()協助我們呼叫右值引數的函式,但是無從得知原本的屬性是什麼,不管進入函式的是左值還是右值move()一律將其轉為右值;相較於forward()這個能夠還原原本的屬性,原本屬性是左值,出來就是還是左值。
如果要使用 move() 解決那至少也要存在著2個函式才可以解決,一個呼叫左值一個呼叫右值,這是因為無法得知原本屬性,只能給使用者自己判斷。
template<class T>
void tran1(T & t){
    fun(t);
}

template<class T>
void tran2(T && t){
    fun(move(t));
}

參考代碼

/*****************************************************************
Name : 
Date : 2017/04/07
By   : CharlotteHonG
Final: 2017/04/07
*****************************************************************/
#include <iostream>
using namespace std;

void fun(int & i){
    cout << "fun L" << endl;
}
void fun(int const & i){
    cout << "fun L const" << endl;
}
void fun(int && i){
    cout << "fun R" << endl;
}
void fun(int const && i){
    cout << "fun R const" << endl;
}

template<class T>
void tran(T && t){
    fun(forward<T>(t));
}
// template 解決了 const 與 non-constt 的問題,可以還原原本屬性
// && 解決了 LR 屬性問題,可以還原原本左右值屬性
// move提供使用者選擇,可以選擇要叫左值還是右值的函式
// forward 解決了引入的右值有了名字變成左值的問題
/*==============================================================*/
int main(int argc, char const *argv[]){
    int a=0;
    int const b=0;
    // fun(a);
    // fun(b);
    // fun(move(a));
    // fun(move(b));

    tran(a);
    tran(b);
    tran(move(a));
    tran(move(b));
    return 0;
}
/*==============================================================*/

2017年4月4日 星期二

c++ typeid() 用法 如何正確的顯示 變數型態名稱的亂碼、異常

c++ typeid() 用法 如何正確的顯示 變數型態名稱的亂碼、異常

typeid() 用法在不同的編譯器不一定有正確的結果顯示
可能會出現亂碼,或是無法辨識到底是什麼名稱
如果顯示異常需要一點技巧,主要的手段來自於
#include <typeinfo>
#include <cxxabi.h>

int status;
char *realname = abi::__cxa_demangle(typeid(obj).name(), 0, 0, &status);
std::cout << realname;
free(realname);
代碼要乾淨一點可以這樣封裝
/*****************************************************************
Name : 正確的顯示型態名稱
Date : 2017/04/04
By   : CharlotteHonG
Final: 2017/04/04

http://stackoverflow.com/questions/789402
*****************************************************************/
#include <iostream>
using namespace std;

#include <typeinfo>
#include <cxxabi.h>
class type_name{
public:
    template <class T>
    type_name(T const t): realname(
        abi::__cxa_demangle(typeid(T).name(), 0, 0, &status))
    {
        cout << realname << endl;
    }
    ~type_name(){
        free(realname);
    }
    int status;
    char *realname;
    #define type_name(i) type_name(std::move(i));
};
//================================================================
int main(int argc, char const *argv[]){
    type_name(1.0);
    type_name('a');
    type_name("a");

    void* i;
    type_name(i);

    class A{} a;
    type_name(a);
}
//================================================================

2017年3月29日 星期三

Android Studio 改主題 Material [sublime text 或 Material Theme UI]

Android Studio 改主題 Material [sublime text 或 Material Theme UI]

最下方有超級懶人包,包含本文的所有設定
可以直接跳下去看,只需要點兩下開啟然後安裝即可

修改 Android Studio 主題 需要的

  • 字體設定
  • Material 文字顏色設定
  • Matarial 環境UI設定

字體設定

這裡推薦一個很好的看字體,非常適合拿來寫程式,可以很有效的區分那些長得很像,0難以區分的文字
官方:Hack Font
安裝即可需要懶人包可以從這裡下載
執行後就會裝好了不會有任何提示
Hack字體懶人包

文字顏色設定

這裡可以下載別人幫你設定好的設定來匯入
注意這會覆蓋你原本的設定
文字配色下載:
你也可以自己搜索有沒有自己喜歡的
好了之後可以調整一下文字大小與字型
間距可以稍微調低,這樣同一個頁面可以塞比較多的行的代碼
改完之後主題還是黑色的跟周邊的黑色不是很搭,需要在做下一步

主題UI設定

這裡可以直接從軟體內的設定找到主題安裝
先從左上角:File -> Setting
然後
之後會要求你重新啟動,啟動就好摟~

會遇到的問題

代碼的地方黑黑的看起來不是很舒服,手動將他調成正常的顏色
進入設定,先另存新檔以免改壞了
對著黑黑的地方按一下左鍵,修改顏色改成 DEE0DF
這兩個改成 82AAFF

下面一點的地方還有一個,改成 10B1FE

超級懶人包

請先確定你的版本是 2.3 版本的
從上面 hepl -> about
執行後即會自行安裝
  • 要先關閉 Android Studio 主程式
  • 注意會刪除原本的設定值,非全新安裝建議自己跑上面流程

參考

2017年3月27日 星期一

C++11 lambda 實際用處與實現

C++11 lambda 實際用處與實現

tags: C++ Concept
C++開始才有的新語法,lambda匿名函式,看起來可能與原本的函式差不多,實際上有個很大的用處,用一句話形容
精簡呼叫方法
而且這種精簡是傳統副程式做不到的精簡

示例

如果你有一個函式
int fun(int a, int b){
    return a+b;
}
你希望它們做陣列的相加你必須這麼輸入
cout << fun(arr[1], arr[2]) << endl;
可是每次都要打arr實在有點擾人,我就只想對arr不對別人了
auto l = [&](int a, int b){
    return fun(arr[a], arr[b]);
};
使用這樣的語意實現副函式無法實現的精簡呼叫方法
cout << l(1, 2) << endl;
你可以省略每次都打arr的麻煩
如果用副函式實現還要輸入arr的指標
int arr_add(int* p, int a, int b){
    return p[a]+p[b];
}
// 使用副程式實現(無法完全精簡還是有個arr)
cout << arr_add(arr, 1, 2) << endl;

優勢

除了可以完全精簡之外,比如說如文中還要導入arr的指標進去,出現在類別的封裝個是惱人,為了這個必須寫一個與資料成員毫無相關的函式。
也可以減少副程式名稱對環境的汙染,也省去了花時間去想函式名字的煩惱,有時候要想個好名稱真的很費時間。

代碼

/*****************************************************************
Name : 
Date : 2017/03/27
By   : CharlotteHonG
Final: 2017/03/27
*****************************************************************/
#include <iostream>
using namespace std;

int fun(int a, int b){
    return a+b;
}

int arr_add(int* p, int a, int b){
    return p[a]+p[b];
}
/*==============================================================*/
int main(int argc, char const *argv[]){
    int arr[]{1, 2, 3, 4, 5};
    // 每次都要打 arr有點擾人
    cout << fun(arr[1], arr[2]) << endl;

    // lambda
    auto l = [&](int a, int b){
        return fun(arr[a], arr[b]);
    };
    // 現在你不用打那麼長了,也不用為了不汙染環境而想函式名字想破腦
    cout << l(1, 2) << endl;

    // 使用副程式實現(無法完全精簡)
    cout << arr_add(arr, 1, 2) << endl;
    return 0;
}
/*==============================================================*/

參考

2017年3月26日 星期日

重載 operator 各項技巧與範例 - 目錄

operator=() 進階重載2 同時實現深度拷貝與淺度拷貝功能

operator=() 進階重載2 同時實現深度拷貝與淺度拷貝功能

tags: operator

補充知識:

如果一個物件成員具有指針,那麼複製時候將存在兩種方式,深度拷貝與淺度拷貝,將兩種拷貝另用其他函式實現在介面使用時並不怎麼好
解決方法,可以利用右值引用來區分等號呼叫的函式,利用此一特性可以區別等號的兩種拷貝方式,而不用額外在重寫
將逐一介紹如實現該功能,以及可能遇到的坑,並提供完整的參考代碼

std::拷貝函式

std提供一個拷貝函式, 會拷貝指針所指之處的值,也就是深度拷貝。
vector<int> v{0, 1, 2}, v2(3);
std::copy(v.begin(), v.begin()+v.size(), v2.begin());
分別需要輸入的是
std::copy(起始指針, 結束指針, 對象起始指針);

重載深度拷貝複製建構函式

深度拷貝的複製建構函式透過 std::copy() 實現
// 複製建構子
List(List const & rhs): len(rhs.len), list(new T[rhs.len]){
    std::copy(rhs.list, rhs.list + len, this->list);
}
需要做的是初始資源空間與長度

重載深度拷貝複製函式

複製函式與複製建構函式的差異於,資源已經事先建立好了,需要先清除本地資源再重建資源,然後再進行賦值。然後也要注意是否會發生將自己複製給自己的情況,不應該浪費效能重新複製。
List & operator=(List const & rhs){
    cout << "copy" << endl;
    // 相同則離開
    if(this != &rhs){
        // 清除本地資源
        this->~List();
        // 重建資源
        this->list = new T[len];
        this->len = rhs.len;
        // 深度拷貝
        std::copy(rhs.list, rhs.list + len, this->list); 
    }
    return (*this);
}

重載移動函式

與複製函式一樣,本地資源已經不需要可以先釋放他,然後再將來源指針複製到本地指針。
需要注意的點是,這樣複製完之後會產生同時兩個指針指向同一個地址,這在釋放時會被釋放兩次,必須將來源指針歸零。
// 移動函式
List & operator=(List && rhs){
    cout << "Move" << endl;
    // 相同則離開
    if(this != &rhs){
        // 清除本地資源
        this->~List();
        // 淺度拷貝
        this->len = rhs.len;
        this->list = rhs.list;
        // 清空來源地址
        rhs.list = nullptr;
        rhs.len = 0;
    }
    return (*this);
}

重載移動建構函式

比較特別的是,由於不需要要求空間,可以直接套用移動函式來完成實作,藉此省去多餘的代碼。
要注意的是這裡也需要使用 std::move() ,否則右值引入的同時,他就具備名字了,會變成左值。
// 移動建構子
List(List && rhs): len(0), list(nullptr){
    (*this) = std::move(rhs);
}

範例代碼

/*****************************************************************
Name : operator 移動語意的實現
Date : 2017/03/25
By   : CharlotteHonG
Final: 2017/04/07
*****************************************************************/
#include <iostream>
#include <iomanip>
#include <numeric>
#include <vector>
#include <initializer_list>
#include <algorithm>
using namespace std;

template <typename T>
class List{
public:
    // 建構子
    List(initializer_list<T> n): len(n.size()), list(new T[len]){
        std::copy(n.begin(), n.end(), this->list);
    }
    // 複製建構子
    List(List const & rhs): len(rhs.len), list(new T[rhs.len]){
        std::copy_n(rhs.list, len, this->list);
    }
    // 移動建構子
    List(List && rhs): len(0), list(nullptr){
        (*this) = std::move(rhs);
    }
    // 解構子
    ~List(){
        if(this->list != nullptr) {
            delete [] this->list;
        }
    }
public:
    List & pri(string name=""){
        if(name != "")
            cout << name << endl;
        for(unsigned i = 0; i < this->len; ++i) {
            cout << setw(3) << (*this).list[i];
        } cout << endl;
        return (*this);
    }
public:
    // 複製函式
    List & operator=(List const & rhs){
        cout << "copy" << endl;
        // 相同則離開
        if(this != &rhs){
            // 清除本地資源
            this->~List();
            // 重建資源
            this->list = new T[len];
            this->len = rhs.len;
            // 深度拷貝
            std::copy_n(rhs.list, len, this->list); 
        }
        return (*this);
    }
    // 移動函式
    List & operator=(List && rhs){
        cout << "Move" << endl;
        // 相同則離開
        if(this != &rhs){
            // 清除本地資源
            this->~List();
            // 淺度拷貝
            this->len = rhs.len;
            this->list = rhs.list;
            // 清空來源地址
            rhs.list = nullptr;
            rhs.len = 0;
        }
        return (*this);
    }
public:
    size_t len;
    T* list;
};
/*==============================================================*/
int main(int argc, char const *argv[]){
    List<int> a{3, 2, 1};
    List<int> b{1, 2, 3};
    a.pri();
    // 移動函式
    a = move(b);
    a.pri();
    // 移動建構函式
    List<int> c = std::move(a);
    c.pri();
    return 0;
}
/*==============================================================*/