0%

在C++11中,可以這樣用已經不是什麼新鮮事了。

1
2
3
4
5
vector<int> vec;
for (int i : vec )
{
cout << i << endl;
}

如果要在自己的container支援這特性的話,需滿足以下條件。

  • Container必須擁有beginend函數,這兩個函數必須回傳一個 Iterator
  • Iterator必須擁有*++ (prefix版)!=這三個operator function。
    以下範例是從C++11 range-based for loops修改而來,加上自己的實驗。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    #include <iostream>
    #include <algorithm>
    using namespace std;
    class IntVector;

    class ConstIter
    {
    public:
    ConstIter(const IntVector* p_vec, int pos)
    : _pos(pos)
    , _p_vec(p_vec)
    { }
    bool operator!= (const ConstIter& other) const
    {
    return _pos != other._pos;
    }
    int operator* () const;
    const ConstIter& operator++ ()
    {
    ++_pos;
    return *this;
    }
    private:
    int _pos;
    const IntVector *_p_vec;
    };

    class Iter
    {
    public:
    Iter(IntVector* p_vec, int pos) : _pos(pos) , _p_vec(p_vec) { }
    bool operator!= (const Iter& other) const
    {
    return _pos != other._pos;
    }
    int& operator* ();
    Iter& operator++ ()
    {
    ++_pos;
    return *this;
    }
    private:
    int _pos;
    IntVector *_p_vec;
    };

    class IntVector
    {
    public:
    int get(int col) const { return _data[col]; }
    int& get(int col) { return _data[col]; }

    IntVector() {}



    Iter begin()
    {
    return Iter(this, 0);
    }

    Iter end()
    {
    return Iter(this, 100);
    }

    ConstIter begin() const
    {
    return ConstIter(this, 0);
    }
    ConstIter end() const
    {
    return ConstIter(this, 100);
    }

    void set(int index, int val)
    {
    _data[index] = val;
    }

    private:
    int _data[100];
    };

    int
    ConstIter::operator* () const
    {
    return _p_vec->get(_pos);
    }

    int&
    Iter::operator* ()
    {
    return _p_vec->get(_pos);
    }

    int main()
    {
    IntVector v;
    for (int i = 0; i < 100; i++)
    v.set(i, i);
    transform(v.begin(), v.end(), v.begin(), [](int v) { return v * 2; });
    for (int& i : v) { i *= 2; }
    for (const int& i : v) { cout << i << endl; }
    }
    在gcc跟clang都能正常運作,不過到了VC12 Debug Mode就編譯失敗了。
    這是由於Checked Iterator這巷特性。
    最快的解決方案是在前面加上
    1
    2
    3
    4
    5
    6
    #ifndef _ITERATOR_DEBUG_LEVEL
    #define _ITERATOR_DEBUG_LEVEL 0
    #else
    #undef _ITERATOR_DEBUG_LEVEL
    #define _ITERATOR_DEBUG_LEVEL 0
    #endif

在整理Concurrency programming資料的時候,發現這個部份被我遺漏了,寫點東西免得忘記。
libdispatch 是由蘋果開發的Concurrency framework,如今也可以在FreeBSD上使用。

FreeBSD Wiki上找來的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <dispatch/dispatch.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
dispatch_queue_t q;
dispatch_time_t t;

q = dispatch_get_main_queue();
t = dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC);

// Print a message and exit after 5 seconds.
dispatch_after(t, q, ^{
printf("block_dispatch\n");
exit(0);
});

dispatch_main();
return (0);
}

看到那個 ^{ .... } 區塊的部份就類似於其他語言的Closure,C++11的lambda expression
至於要編譯這段程式碼,就需要

1
# clang -Wall -Werror -fblocks -L/usr/local/lib -I/usr/local/include -o test test.c -ldispatch

Blocks是Clang的Extension,更多資訊可以參考Programming with C Blocks,GCC不支援,至於libdispatch需要在ports下事先安裝。編譯的時候要記得加上-fblockss
當然,也可以有無Blocks的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <dispatch/dispatch.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>

void
deferred_code(__unused void *arg)
{

printf("block_dispatch\n");
exit(0);
}

int main(int argc, char *argv[])
{
dispatch_queue_t q;
dispatch_time_t t;

q = dispatch_get_main_queue();
t = dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC);

dispatch_after_f(t, q, NULL, deferred_code);

dispatch_main();
return (0);
}

編譯的時候就可以拿掉-fblocks

1
# clang -Wall -Werror -I/usr/local/include -L/usr/local/lib -o test2 test2.c -ldispatch

除了Cuncurrency之外,Closure的觀念也在很多程式語言開枝散葉了。

這個pdf有對libdispatch作個簡單的介紹。
在各語言下都有類似libdispatch這樣的Framework

雖然大部分時間都在Windows/Linux底下打轉,FreeBSD被我晾在一旁(時間不夠用Orz)
不過趁著FreeBSD 10.0 Release
把自己的9.1-Release升級到10.0-Releae,並把過程記錄下來。

1
2
3
4
5
6
7
# freebsd-update upgrade -r 10.0-RELEASE
# freebsd-update install -r 10.0-RELEASE
# reboot
# freebsd-update install
# reboot
# uname -ra
FreeBSD freebsd 10.0-RELEASE FreeBSD 10.0-RELEASE #0 r260789: Thu Jan 16 22:34:59 UTC 2014 root@snap.freebsd.org:/usr/obj/usr/src/sys/GENERIC amd64

安裝完10.0之後,發現gcc不存在於,全部用clang取代掉了。
另外就是使用 pkgng 來當做新的Package manager,用法就類似於 apt 或 yum。

1
2
3
4
5
6
7
8
9
# pkg update
# pkg search vim
vim-7.4.110_3
vim-lite-7.4.110
vimpager-1.8.3
xpi-vimperator-3.5
# pkg install vim-7.4.110_3
# pkg remove vim-7.4.110_3
# pkg autoremove

更多的使用可以參考pkgng: First look at FreeBSD’s new package manager

解決Concurrency中shared infomation的方法不只一總,同樣是Message-based syste,Channel不過跟Actor model不同

  • Actor是直接跟其他Actor作溝通
  • Channel是兩端透過一個Channel連接,然後透過Channel傳輸到另一端,類似Unix的pipe

「Go](http://golang.org/)是最著名使用Channelmodel當做標準配備的程式語言。
看看Go網站上提供的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// A concurrent prime sieve

package main

import "fmt"

// Send the sequence 2, 3, 4, ... to channel 'ch'.
func Generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in <-chan int, out chan<- int, prime int) {
for {
i := <-in // Receive value from 'in'.
if i%prime != 0 {
out <- i // Send 'i' to 'out'.
}
}
}

// The prime sieve: Daisy-chain Filter processes.
func main() {
ch := make(chan int) // Create a new channel.
go Generate(ch) // Launch Generate goroutine.
for i := 0; i < 10; i++ {
prime := <-ch
fmt.Println(prime)
ch1 := make(chan int)
go Filter(ch, ch1, prime)
ch = ch1
}
}

原先的ch 是 [2, 3, 4, 5, 6, ….]的Channnel
在從for-loop中拿出2之後,變成[3, 4, 5, 6, …]
再度經過Filter函數,將2的倍數濾掉之後, ch1就是[3, 5, 7, 9, ….]的Channel
之後將ch1複製到ch,重複以上的動作。

這邊有Scala跟Go對Cuncurrency的比較
這邊是Go cunnerency的投影片

About Actor Model

在Concurrency programming流行之後,Actor Model又重新熱門起來了。
一個Actor要做的就是以下幾件事情

  • 建立其他 Actor
  • 對其他Actor發送消息
  • 接收並處理消息
    因此,Erlang可以這樣寫
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    loop() ->
    receive
    {From, {store, Key, Value}} ->
    put(Key, {ok, Value}),
    From ! {kvs, true},
    loop();
    {From, {lookup, Key}} ->
    From ! {kvs, get(Key)},
    loop()
    end.
    而沒有支援Tail Recursion的程式語言,如Scala等,用Loop來模擬Tail Recursion。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Ping(count: int, pong: Actor) extends Actor {
    def act() {
    var pingsLeft = count - 1
    pong ! Ping
    while (true) {
    receive {
    case Pong =>
    if (pingsLeft % 1000 == 0)
    Console.println("Ping: pong")
    if (pingsLeft > 0) {
    pong ! Ping
    pingsLeft -= 1
    } else {
    Console.println("Ping: stop")
    pong ! Stop
    exit()
    }
    }
    }
    }
    }
    至於C++,有Theron這套Framework可以建構Actor,至於Event-based或是Thread-based就取決於用途了。

Why use Actor Model

根據網路上找到的資料,把它歸類為這幾個理由

  • No shared infomation,在多個Thread共享一段資料的時候,需要用Atomic operation或是Locks作保護。除了性能可能有所損失的話,不當的Lock sequence也會造成Dead lock。而Actor可以將information保存於單一個Actor當中。這樣就少去了錯誤同步的風險。 當然,消除shared infomation的方案還有Functional programming。
  • Asynchronous,雖然Scala有提供同步版本的API。不過大部分的Actor implementation都是Asynchronous。不必耗費CPU Resource在等待完成的時刻。

在C++中,new / delete 總共有三種用法。new跟delete的用法類似,所以就以new來示範。

operator new

詳細細節可以參考operator newoperator new, operator new[]
可以透過function overload訂製自己的new和delete動作。
operater new/delete就相對於C語言的malloc/free。因此透過operator new分配到的記憶體就該用operator delete釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Object {
Object() {std::cout << '*' << std::endl; } // print an asterisk for each construction
void* operator new(size_t size)
{
return ::operator new(size);
}
void operator delete(void *ptr)
{
return ::operator delete(ptr);
}
void* operator new[](size_t size)
{
return ::operator new[](size);
}
void operator delete[](void *ptr)
{
return ::operator delete[](ptr);
}
};
Object *obj = new Object;
delete obj;

這時就會使用Object裡面的new/delete的函式了。

Placement new

Placement new只是operator new的一種overload版本

1
2
3
4
inline void *operator new(size_t, void *_Where) 
{
return (_Where);
}

詳細使用可以參考C++ FAQ中的介紹。
我們可以從任何記憶體位置(在Heap或是Stack都行)進行new constructor的動作。

1
2
3
4
5
6
7
8
void *pMemory = malloc(sizeof(Object));
Object *pObj1 = new (pMemory) Object;
pObj1->~Object();
free(pMemory);

char stackObj[sizeof(Object)];
Object *pObj2 = new (stackObj) Object;
pObj2->~Object();

Placement new的存在是有意義的,可以自由控制Memory的取得方式,可以從Stack/Heap/Memory Pool中取得記憶體,對於某些情景之下有更大的

new operator

這大概是最常見的動作,基本上就是以下兩個步驟構成

  • 呼叫 operator new
  • 呼叫 Constructor
    這個順序是固定的,無法opverload,這是C++ Standard所規定的。

看慣了x86的Intel Syntax,對於AT&T的語法還是不習慣。
使用以下方式可以將gcc編譯出來的Code變成Intel Syntax。

1
$ gcc test.c -S -masm=intel

這只支援gcc,clang不行。

突然想到,順手把他記下來吧。
在C++當中,如果要自己定義++和–這兩個operator,需要不同的Function signature。

1
2
3
4
5
6
7
8
class Date {
//...
public:
Date& operator++(); //prefix
Date& operator--(); //prefix
Date& operator++(int unused); //postfix
Date& operator--(int unused); //postfix
};

最大的差別就在於Postfix裡面有個未使用的參數。至於為什麼要這麼設計,可以參考這裡

這種技巧很常在Open Source的專案中看到,看了Arrays of Length Zero原本以為是GCC獨有Extensionm,後來發現Clang跟VC都能使用,記錄一下。

1
2
3
4
5
6
7
8
9
10
11
12
struct datapacket
{
int size;
int data[0];
];
void f()
{
struct datapacket *pkt =
malloc( sizeof(struct datapacket) +
sizeof(int)*9 );
/* further code . . . */
}

這邊的data[0]不佔記憶體空間,因此sizeof(struct datapacket)大小相當於sizeof(int),而data就是一個記憶體指標,指向一塊連續的記憶體。