0%

在剛落幕的CppCon的投影片中看到這個tTrick,順手就記下來。果然C/C++是越學越不會的語言啊。

1
2
3
4
5
6
7
8
template<typename T>
void foo(T x)
{
puts(__PRETTY_FUNCTION__); // MSVC: __FUNCSIG__
}
foo(4);
foo(4.2);
foo("hello");

如果是VC的話記得使用__FUNCSIG__
最大的用途是幫忙Debug Template Function…

Transducer原先是在Clojure 1.7被提出的新觀念,由於覺得這觀念實在很有趣。看了幾篇文章之後,打算寫些東西。試著一步一步前進,加深自己印象。

What is Transducer

Transducer 是遊 Transfromreducer 兩個字合成出來的,而
– Transform: 轉換,由 A 變成 B
– Reducer: 接受輸入和先前的狀態,產生一個新的狀態

從一個簡單範例開始

1
2
3
4
5
6
7
8
9
vector<int> calc(const vector<int> &vec)
{
vector<int> result;
for (const auto &v : vec) {
if (v % 2 == 0)
result.push_back(v / 2);
}
return result;
}

這段程式很簡單就知道他在做什麼了,不過他還是有一些圈點
例如新增了一個條件, 不能修改原有條件。
只能寫個95%相像的程式,例如

1
2
3
4
5
6
7
8
9
vector<int> calc2(const vector<int> &vec)
{
vector<int> result;
for (const auto &v : vec) {
if (v > 5)
result.push_back(v * v);
}
return result;
}

雖然一樣能夠解決問題,不過寫久了也是會覺得枯燥乏味。
這邊就缺少了Functional programming中的composability
接著用Functional Progrmaming的觀點來看這問題

來點Functional Programming

先來個C++版的MapFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <template <typename...> class C, typename T, typename ...TArgs,  typename Fn>
auto Map(Fn fn, const C<T, TArgs...> &container) {
C<T, TArgs...> result;
for (const auto &item : container)
result.push_back(fn(item));
return result;
}
template <typename In, typename Out, typename Pred>
Out filter(In first, In last, Out out, Pred pred) {
for (; first != last; ++first)
if (pred(*first))
*out++ = *first;
return out;
}

重新構建我們的函數

1
2
3
4
5
6
vector<int> calc(const vector<int> &vec)
{
auto div2 = [](auto v) { return v / 2; };
auto isEven = [](auto v) { return v % 2 == 0; };
return Map(div2, Filter(isEven, vec));
}

這下子可以用組合來看這個問題了,不過這方法的問題也顯而易見。仙不論C++中間產物的影響

  • 原先的Solution只要一次Lopp就結束了
  • 這方法需要兩次Loop,一次Filter,一次Map
    這就是改造的地方,因此Reducer上場了

    Reduce

    1
    2
    3
    4
    5
    6
    7
    template <template <typename...> class C, typename T, typename ...TArgs,
    typename S, typename Rf>
    auto reduce(const C<T, TArgs...> &container, S state, Rf step) {
    for (const auto &c : container)
    state = step(state, c);
    return state;
    }
    這個救跟std::accumulate作法差不多。
    加上一個helper function
    1
    2
    3
    4
    auto concat = [](auto result, auto input) {
    result.push_back(input);
    return result;
    };
    上面的Map跟Filter能用Reduce重新實作了
    Map部分
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    auto mapping = [](auto fn) {
    return [=](auto step) {
    return [=](auto s, auto ...ins) {
    return step(s, fn(ins...));
    };
    };
    };
    template <template <typename...> class C, typename T, typename ...TArgs, typename Fn>
    auto Map(Fn fn, const C<T, TArgs...> &container) {
    using retType = C<T, TArgs...>;

    return reduce(container, retType(), mapping(fn)(concat));
    }
    Filter部分
    1
    2
    3
    4
    5
    6
    7
    auto filtering = [](auto pred) {
    return [=](auto step) {
    return [=](auto s, auto ...ins) {
    return pred(ins...) ? step(s, ins...) : s;
    };
    };
    };
    Compose
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    auto compose = [](auto f) {
    return [=](auto g) {
    return [=](auto ...ins) {
    return f(g(ins...));
    };
    };
    };
    template <template <typename...> class C, typename T, typename ...TArgs, typename Pred>
    auto Filter(Pred pred, const C<T, TArgs...> &container) {
    using retType = C<T, TArgs...>;
    return reduce(container, retType(), filtering(pred)(concat));
    }
    我們可以甩開 Map 跟 Filter, 重新打造函數,這下只用到 Reduce,太神奇了
    1
    2
    3
    4
    5
    6
    vector<int> calc(const vector<int> &vec)
    {
    auto div2 = [](auto v) { return v / 2; };
    auto isEven = [](auto v) { return v % 2 == 0; };
    return reduce(vec, vector<int>(), filtering(isEven)(mapping(div2)(concat)));
    }
    加上Compose的話,威力就更大了

    Compose

    定義一個簡單的Compose實作
    1
    2
    3
    4
    5
    auto compose = [](auto f, auto g) {
    return [=](auto x) {
    return f(g(x));
    };
    };
    重新實作我們的 calc function
    1
    2
    3
    4
    5
    6
    7
    vector<int> calc(const vector<int> &vec)
    {
    auto div2 = [](auto v) { return v / 2; };
    auto isEven = [](auto v) { return v % 2 == 0; };
    auto comp = compose(filtering(isEven), mapping(div2));
    return reduce(vec, vector<int>(), comp(concat));
    }
    一切就是這麼神奇

Reference

CSP and transducers in JavaScript
Transducers From Clojure to C++
transducers in C++ 14

Build GCC

怕忘記,寫下來好了。先下載GCC 4.9的Source code且解壓縮。

1
2
3
4
5
6
$ cd gcc-6.1.0
$ ./contrib/download_prerequisites ## 下載GMP, MPFR跟MPC等Library在當前目錄
$ mkdir build && cd build
$ ../configure --enable-checking=release --enable-languages=c,c++ --disable-multilib
$ make -j 4
$ sudo make install

在下configure的時候可以加參數,可以參考這裡
在Debian體系下,利用update-alternatives來替換掉/usr/bin的gcc symbol link

1
$ update-alternatives --install /usr/bin/gcc gcc /usr/local/bin/x86_64-unknown-linux-gnu-gcc-6.1.0 40

關於update-alternatives的用法可參考這裡

Reference build

為了工作上的需要,試著把FFMpeg放入Browser中,雖然成功了,但是效果沒想像的好,尤其是在Mobile Browsers上。
不過這也是WebAssembly打算改善的問題,目前只有Edge跟Firefox支援Asm.js程度比較高。
在FFmpeg這項目吃盡苦頭,寫個筆記紀錄怎麼認真玩Emscripten。
我是參考audioconverter.js不過用他的Script無法使用。只好自行摸索出適合的方式。

我的目標市 FFmpeg + fdk_aac ,接下來的教學就是如何做出一個包涵這兩樣的 Javascript

Build FDK AAC

這個跟一般的玩Emscripten玩法沒差太多

1
2
3
4
5
6
7
8
9
10
11
$ git clone https://github.com/mstorsjo/fdk-aacsed -i 's/gcc/emcc/g' config.mak
$ cd fdk-aac
$ ./autogen.sh
$ emconfigure ./configure --prefix=/home/hm/local
$ emmake make
$ make install
```sed -i 's/gcc/emcc/g' config.makdd

### Build FFMpeg
這才是花了很多時間在Try and Error上,照AudioConvert.JS那樣寫對我來說就是沒用
因為原先emconfigure的方式無法抓到 libfdk_aac,只好手動用原先的方式來作

$ cd ffmpeg
$ PKG_CONFIG_PATH=/home/hm/local/lib/pkgconfig/ ./configure –disable-asm –disable-outdevs –disable-indevs –disable-filters –disable-bsfs –disable-decoders –enable-decoder=mp3,h264 –disable-demuxers –enable-demuxer=mpegts –disable-muxers –enable-muxer=mpegts –disable-protocols –enable-protocol=file –enable-libfdk-aac –disable-encoders –enable-encoder=libfdk_aac –enable-filter=aresample –disable-parsers –disable-doc –disable-stripping –disable-protocol=rtp,tcp,udp,http –disable-pthreads –disable-debug –disable-network –enable-parser=h264 –disable-filter=crop –extra-cflags=-I/home/hm/local/include

1
產生完config.mak之後在手動改掉

$ sed -i ‘s/gcc/emcc/g’ config.mak
$ sed -i ‘s/g++/emcc/g’ config.mak
$ sed -i ‘s/VALGRIND_H 1/VALGRIND_H 0/g’ config.h
$ make

1
如果沒意外的話彙編出**不能動**的ffmpeg,不過在意料之中,編譯過程中也會告訴你沒有找到`fdk-aac`,這都無所謂,畢竟這只是中間產物,要進行最後的動作才行。

$ mv ffmpeg ffmpeg.bc
$ emcc -O2 ffmpeg.bc /home/hm/local/lib/libfdk-aac.a -o ffmpeg.js

1
接著我們用nodejs試著執行看看

$ node ffmpeg.js
ffmpeg version 3.1.1 Copyright (c) 2000-2016 the FFmpeg developers

1
2
3
4
成功了,不過還是不能在網頁上用
### Runs inside browsers
參考AudioConvert.js的方式,將`ffmpeg-pre.js`和`ffmpeg-post.js`複製出來
進行編譯

$ emcc -g4 -Os –memory-init-file 0 ffmpeg.bc /home/hm/local/lib/libfdk-aac.a -o ffmpeg.js –pre-js ffmpeg_pre.js –post-js ffmpeg_post.js

```
然後將ffmpeg.js複製到audioconvert.js中的測試網頁中,就能看到他能夠動作了

Conclusion

emcc的表現真的有夠怪異,如果在網頁版的編譯加上-g3以上等級的debuger info,編出來的東西跟本部能用。
編譯的過程更是痛苦連連,效果也不如想像中的好
希望WebAssembly可以彌補這一塊

根據這篇這篇,C++17的最終定案即將出爐。之前喊得狒狒洋洋的moduleconceptcoroutine沒有一個列為標準配備。相對之前喊得狒狒洋洋的大變動。還真是雷聲大雨點小。
不過還是有些實用的技術可以馬上用到,學習曲線沒那麼高..
不過這年頭每個程式語言都在新增新特性,像C語言這種實在難能可貴。不過C太低階了,開發大程式實在很麻煩。真是兩難

Structured bindings

之前說過,C++沒有return multiple value的機制,要做出類似的小果只能這麼做。

1
2
3
4
std::tuple<bool, int> getValue();
bool retB;
int retInt;
std::tie(retB, retInt) = getValue();

可以用,不過不太美觀。到了C++17有了更直觀的方法

1
auto [retB, retInt] = getValue();

這樣就很像go的語法了。更多的討論可以看Returning multiple values from functions in C++

If statement with initializer

這也是另一個我覺得很棒的點
直接看範例,為進化前

1
2
3
4
5
6
7
8
9
status_code foo() { 
{
status_code c = bar();
if (c != SUCCESS) {
return c;
}
}
// ...
}

進化後,可能的情況

1
2
3
4
5
6
status_code foo() {
if (status_code c = bar(); c != SUCCESS) {
return c;
}
// ...
}

希望這兩點未來C語言也會加回去啊

C/C++寫久了,為了轉換心情,開發了一個玩具Website Parser。把太過複雜的網頁元素清掉,只留下Title跟必要的資訊。
採用的是Python3跟BeautifulSoup4,雖然Python的文章看了不少。不過這倒是第一次認真寫點東西(雖然也只是個玩具)

獲取網頁資訊

用最簡單的方式來獲取網頁,方法不是最好,但是可以用。

1
2
3
4
def getHtml(url):
htmlDoc = request.urlopen(url).read()
htmlDoc = htmlDoc.decode('UTF-8')
return htmlDoc

將html轉成 Beautiful Soup Object

1
soup = BeautifulSoup(htmlDoc, "lxml")

砍掉無謂的網頁元件

砍掉所有符合條件的 Tag

1
2
for script in soup.find_all('script'):
script.decompose()

砍掉特定的 Tag

1
2
3
div = soup.find("div", {"id": "full-btm"})
if div is not None:
div.decompose()

砍掉 Tag中的某個屬性

1
2
3
div = soup.find("div", {"id": "full-btm"})
if div is not None:
del div["attribute"]

把清除過的 Soup 存回 HTML

1
2
3
4
html = soup.prettify("utf-8")

with open("output.html", "wb") as file:
file.write(html)

雖然只是個玩具,不過至少對Python有多一點感覺了,寫完之後才發現Sanitize HTML with Beautiful Soup這篇早就有了,真是後知後覺好幾年。

好久沒寫些東西了,雖然這篇沒什麼實用價值。在C++這種語言要模擬FP的Closure是做得到,不過不好用
這邊的Clouse跟C++的Lambda Closure不太相同。
先看正常版的C++ Code

1
2
3
4
5
6
struct Counter {
int value = 0;
void inc() { value++; }
};
Counter c;
c.inc();

一目了然的程式,也不用解釋太多。
模擬版的FP Closure

1
2
3
4
5
6
7
8
9
10
11
12
struct Counter {
int value = 0;
};
std::function<void(void)> make_inc()
{
std::shared_ptr<Counter> obj = make_shared<Counter>();
return [obj]() {
obj->value++;
};
}
std::function<void(void)> inc = make_inc();
inc();

真是囉唆。 Javascript還可以輸出多個函數。如果要用C++也能作到,像這樣

1
2
3
4
5
6
7
8
9
10
11
12
typedef std::function<void(void)> Func;
std::tuple<Func, Func> make_counter()
{
std::shared_ptr<Counter> obj = make_shared<Counter>();
Func inc = [obj]() { obj->value++; };
Func dec = [obj]() { obj->value--; };
return make_tuple(inc, dec);
}
auto funcs = make_counter();
Func inc = std::get<0>(funcs);
Func dec = std::get<1>(funcs);
inc();

tuple和get來抓取函數時再不怎麼漂亮,也許C++17的Structured bindings可以改善這個問題。

Reference

Returning multiple values from functions in C++
Emulating C++17 Structured Bindings in C++14

Stackless coroutine

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
#include <iostream>
#include <boost/asio/yield.hpp>

int foo(boost::asio::coroutine& ct)
{
std::cout << "before reenter" << std::endl;

reenter(ct)
{
std::cout << "before yield1" << std::endl;
yield std::cout << "yield1" << std::endl;
std::cout << "before yield2" << std::endl;
yield return 1;
}

std::cout << "after reenter" << std::endl;
return 2;
}

int main(int argc, char* argv[])
{
boost::asio::coroutine ct;
while (!ct.is_complete())
{
int ret = foo(ct);
std::cout << "return:" << ret << std::endl;
}
return 0;
}

有些時候,我們不想要有libstdc++.so的相依性,而只要依賴libc,如果要整個重寫原先的C++ Code花費太大。

1
2
3
4
5
6
#include <iostream>
int main()
{
std::cout << "Hello" << std::endl;
return 0;
}

看一下相依性

1
2
3
4
5
6
7
8
$ g++ test.cpp -o test
$ ldd ./test
linux-vdso.so.1 => (0x00007fff184de000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f9cfb9b9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9cfb5f4000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f9cfb2ee000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9cfbcbd000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f9cfb0d8000)

但是如果我們這樣坐的話,一切就會不同

1
2
3
4
5
6
7
8
$ g++ test.cpp -o test  -static-libstdc++ -static-libgcc
$ ldd ./test
linux-vdso.so.1 => (0x00007ffee379d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f72ef99e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f72efd63000)
$ g++ test.cpp -o test -static
$ ldd ./test
not a dynamic executable

這邊有三個選項
-static-libstdc++ 連結libstdc++.a
-static-libgcc 連結libgcc.a,如果只有-static-libgcc而沒有-static-libstdc++不起作用,但是反過來不是那麼一回事。
– ‘-static’ 去存所有相依性

Reference

How to Statically Link C and C++ Programs on Linux with gcc

以下的程式碼哪裡有問題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pthread_mutex_t mutex;
pthread_cond_t cond;
std::queue<int> q;
int Get()
{
pthread_mutex_lock(&mutex);
if (q.empty())
pthread_cond_wait(&cond, &mutex);
int item = q.front();
pthread_mutex_unlock(&mutex);
return item;
}
void Add(int item)
{
pthread_mutex_lock(&mutex);
q.push(item);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}

Spurious wakeup

所謂的Spurious wakeup就是被Blocking的Thread認為自己滿足條件而被喚醒,而事實上不是這麼一回事。例如

Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations.

由於有Spurious wakeup的發生,為了程式的正確性,需要為此作處理。

Consumer’s Problem

看上面的

1
2
if (q.empty())
pthread_cond_wait(&cond, &mutex);

當Spurious wakeup發生時,這個條件不一定滿足,因此要把if改成while

Producer’s Problem

由於condition variable的signal跟mutex的unlock是獨立事件,因此兩個的順序不重要,正確性都得以保證。不過這樣子的寫法一樣會造成Spurious wakeu的問題

pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);

分析如下

  1. Thread A starts waiting for items to be added to a threadsafe queue.
  2. Thread B inserts an item on the queue. After unlocking the queue, but before it issues the signal, a context switch occurs.
  3. Thread C inserts an item on the queue, and issues the cvar signal.
  4. Thread A wakes up, and processes both items. It then goes back to waiting on the queue.
  5. Thread B resumes, and signals the cvar.
  6. Thread A wakes up, then immediately goes back to sleep, because the queue is empty.

Reference

用条件变量实现事件等待器的正确与错误做法
– [signal and unlock order