c++

淺談C++ bind function

我們之前在implementRead/Write Lock的時候 wait function裡面我們用了bind 我當時輕描淡寫的帶過了 但其實裡面是有些學問的 今天就來把它一探究竟

其實這在很多語言都有 不要因為你不是main c++的就跳過

正文開始 遙想年少輕狂時 曾有這麼一段過去

readerQ.wait(lk, bind(&RWLock::no_one_writing, this));

哇賽 這是什麼東西?

bind

先從bind開始講起 其實bind只是一個function wrapper

一個例子勝過千言萬語 今天我們有一個很簡單的func 把兩個數加起來

int add(int x, int y){
    return x + y;    
}

如果我們今天想寫一個function always return 1 + 2

我們就不用重複寫一個很像的function然後把裡面的變數寫死

int add12(){ 
        return 1 + 2;    
}

我們直接用一個wrapper把它包起來

auto add12 = bind(add, 1, 2);
cout << add12() << endl; // print 3

第一個參數給function pointer, 之後的參數給那個function的參數 看你要怎麼wrap

好處有2

  1. Code reusable, 你現在看了沒感覺是因為他只是一個plus 如果裡面的logic再複雜一點 你就會發現bind的好處 你不用把add裡面的東西全copy出來然後寫死你的變數 事實上很多bug都是這麼來的

  2. Easier to maintain, 如果今天變成x+y+1 你不用去每個function去改 只要改add就好了

寫了老半天 上面兩點講的好像是同一件事

Alt text

總之 bind return一個新的function 這個新function也可以給參數

auto add2 = bind(add, placeholders::_1, 2);
cout << add2(6) << endl; // print 8

意思就是add2的第一個參數 當成add的第一個參數 參數數量跟順序也不用一定

auto add_second_third_arg = bind( add, placeholders::_3, placeholders::_2);
cout << add_second_third_arg(6, 4, 3) << endl; // print 7

所以訣竅就是 那個要被拿來包的(add) 要寫的通用一點(雖然大多情況是你從外面import的function) 那你bind完包一層之後 就可以customize成適合你application的function

懂了 那回到RW lock

bool no_one_writing(int active_readers, int active_writers){
    return active_writers == 0 || active_readers > 0;
}
class RWLock {
    public:
        void ReadLock() {
            std::unique_lock<std::mutex> lk(shared);
            readerQ.wait(lk, bind(no_one_writing, active_readers, active_writers));
            ++active_readers;
            lk.unlock();
        }
};

看完bind教學 我必須把no_one_writing需要的東西用wrapper包給他 看起來挺完美 但這樣不對 因為你active_readers和active_writers都是pass by value 他之後被叫醒 去確認condition的時候會看跟第一次去睡的同一個值 所以你會永遠起來後就睡覺

簡單的修改 改成傳reference就可以

readerQ.wait(lk, bind(no_one_writing, ref(active_readers), ref(active_writers)));

搞定 可是缺點有2

1.在實際的應用上 很有可能你的RW Lock會包成一個library給別人用 所以no_one_writing()應該要屬於我們data structure的一個member function

2.這樣不酷

再回來看一下我們最後的implementation

readerQ.wait(lk, bind(&RWLock::no_one_writing, this));

RWLock::no_one_writing 好懂 就是丟member function給bind的第一個參數 可是RWLock::no_one_writing 又沒有input參數 你bind的時候給其他參數幹嘛? 這個this又是什麼?

消失的this

The hidden “this” pointer

在一個class的member function裡面 事實上都會比原本的參數再多一個隱藏變數this, this是一個constant pointer指向call這個function的object

learncpp的例子

class Simple
{
    private:
        int m_id;

    public:
        Simple(int id)
        {
            setID(id);
        }

        void setID(int id) { m_id = id; }
        int getID() { return m_id; }
};

compiler會把一個class的member function多一個變數 實際上的setID()的signature變成這樣

void setID(Simple* const this, int id) { this->m_id = id; }

別人call setID的時候原本是這樣call

simple.setID(2);

compiler會解讀成這樣

setID(&simple, 2);

事實上這樣的程式會電腦來說比較簡單 你就直接把你需要被改變的object address一起當function參數給我 我直接改this的變數的值

Q1: Simple* const this?? const不是不可以變的意思嗎?

A1: const擺在這裡代表this是一個const pointer 代表說這個pointer只能指到一個固定的address 指的地方不能改 那如果const擺前面 const Simple* this 那就是this的值不能改 再具體一點 如果this是一個房子的地址 前者是地址不能變 後者是房子裡面住的人不能變

Q2: 任何member funciton都有hidden this嗎

A2: 除了static member function之外 static member function就是你不需要有object也可以call的function 既然是這麼定義的 那當然就不會有this pointer

讀到這裡 再回來看一下我們最後的implementation

readerQ.wait(lk, bind(&RWLock::no_one_writing, this));

有沒有比第一次看Part4-2的時候有感覺了呢

事實上 no_one_writing還是有吃一個變數this, 所以我們在用wrapper包的時候 他會預期你要給他一個變數 那是給什麼變數呢 就是給相同的this 也就是這個RW lock本身

C++ wait document

通透了之後 大家一起來看一下現在網路上關於wait的doc

cplusplus.com conditional variable/wait

cppreference conditional variable/wait

恩…看起來作者的要求只有

這個範例code要跑得起來

沒了 看完之後 知道有wait這麼個function 好謝謝再聯絡

當我真的要用wait去實作個read write lock根本不知道該怎麼寫啊 他的mutex跟condition variable居然就用global variable唬攏過去 這你敢信?

Alt text

沒關係 別怕 有我在 我先幫你把前方未知的路走通了 不只教你wait怎麼用/為什麼用 還教你怎麼在wait的時候 丟進其他的member function 這麼好的部落格哪裡找

謝謝收看