0%

在電腦裡面,有很多Resource的存在,除了最常見的Memory之外,還有FILEHANDLE等以指標為形,卻有自己的Destroy function的資源。而這種資源該怎麼管理比較好,就是一門學問了。

C語言

翻閱很多Open source的Project之後,發現大部分的作法都類似這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef void(*freefunc)(void *);
struct Object {
void *obj_;
freefunc func_;
};
void custom_alloc(Object *obj, void *obj_, void(*freefunc)(void *))
{
obj->obj_ = obj_;
obj->func_ = freefunc;
}
void custom_free(Object *obj)
{
if (obj->obj_)
{
if (obj->func_)
obj->func_(obj->obj_);
else
free(obj->obj_);
}
}
custom_alloc(&obj, malloc(10), NULL);
custom_free(&obj);
custom_alloc(&obj2, fopen("log.txt", "rb"), (freefunc)(fclose));
custom_free(&obj2);

由於任何指標型態都能跟void 互轉,所以這種作法行得通。
不過由於C語言的Type system很弱,一旦轉成void
之後,無法檢查支援先的Type,如果上面的fclose換成free,編譯器不會警告你任何東西,只有在Runtime才會知道這邊會Core dump。

C++98

由於auto_ptr只能管理Memory之類的Resource,所以我們必須要自行打造輪子。
可以學習C++11中的unique_ptr帶入Destroy function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
class CustomResource {
T* obj_;
typedef void (*freeFunc)(T *);
freeFunc func_;
public:
CustomResource(T* obj) : obj_(obj), func_(NULL) {}
CustomResource(T* obj, freeFunc func) : obj_(obj), func_(func) {}
~CustomResource() {
if (func_)
func_(obj_);
else
delete obj_;
}
};
CustomResource<int> m(new int);
CustomResource<FILE> obj(fopen("log.txt", "w"), (void (*)(FILE *))fclose);

雖然這樣子可以盡量避免掉呵叫錯誤Destory function的問題,不過如果硬要cast function prototype也是無法避免。
測試了一下C++11對這種錯誤也沒辦法

1
2
unique_ptr<FILE, int(*)(FILE *)>(fopen("log.txt", "w"), free); // Compile error
unique_ptr<FILE, void (*)(void *)>(fopen("log.txt", "w"), free); // Compile success, but the result is incorrect

另外一個發法是透過Template跟template specialization來解決

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename T>
class CustomResource {
T* obj_;
public:
CustomResource(T* obj) : obj_(obj) {}
~CustomResource() {
delete obj_;
}
};
template <>
class CustomResource<FILE> {
FILE* obj_;
public:
CustomResource(FILE* obj) : obj_(obj) {}
~CustomResource() {
if (obj_)
fclose(obj_);
}
};
CustomResource<int> m(new int);
CustomResource<FILE> obj(fopen("log.txt", "w"));

這樣雖然可以解決,不過美增加一種Resource,就會增加一大堆類似的程式碼,因此,C++中罕見的Template template parameter就可以在此發揮所長。
可以參考Writing a Smart Handle class using template template parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <typename T>
struct destory {
static void free(T * obj)
{
delete obj;
}
};
template <>
struct destory<FILE> {
static void free(FILE * obj)
{
fclose(obj);
}
};
template <typename T, typename Destroy = destory<T>>
class CustomResource {
T* obj_;
public:
CustomResource(T* obj) : obj_(obj) {}
~CustomResource() {
if (obj_) Destroy::free(obj_);
}
};

不過就算把fclose換成free也不會有任何警告,這問題還真是難解。

C++11/C++14

在之前提過了,可以透過unique_ptr跟custom destructor來達成同樣的目的。

1
unique_ptr<FILE, int (*)(FILE *)> obj(fopen("log.txt", "w"), fclose);

C++14之後,新增了make_unique這個函數,我們可以仿造這方式,寫出一個make_resource的函數。
參考這一篇C++14 and SDL2: Managing Resources寫來的。

1
2
3
4
5
6
7
8
9
template<typename Creator, typename Destructor, typename... Arguments>
auto make_resource(Creator c, Destructor d, Arguments&&... args)
{
auto r = c(std::forward<Arguments>(args)...);
if (!r) { throw std::runtime_error {"Unable to create resource"}; }
typedef typename std::decay<decltype(*r)>::type ResourceType;
return std::unique_ptr<ResourceType, void(*)(ResourceType*)>(r, d);
}
auto obj = make_resource(fopen, (void (*)(FILE *))fclose, "log.txt", "w");

這段程式碼的auto跟decltype已經有個抵了,部過decay還看不懂。有時間在研究吧

以往在C++98寫的Code,䅰習慣用Raw Pointer來操作Object,在C++11/14之後,有了更安全的面貌。少了new/delete之後,接觸到Memory Leak的機會變少了。

Insert Element into container

1
2
3
deque<Object *> objs;
for (int i = 0; i < 3; i++)
objs.push_back(new Object());

現在可以這麼寫

1
2
3
deque<unique_ptr<Object>> objs;
for (int i = 0; i < 3; i++)
objs.push_back(make_unique<Object>());

C++11有make_shared部過沒有make_unique,到C++14才加入,部過我們可以打造個一模一樣的版本。

1
2
3
4
5
6
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
for (int i = 0; i < 3; i++)
objs.push_back(make_unique<Object>(i));

Erase element from Container

以往的寫法大概就像這樣

1
2
3
4
5
6
deque<Object*>::iterator  it = std::find_if(objs.begin(), objs.end(), [](const Object *obj) -> bool { ... });

if (it != objs.end()) {
delete *it;
objs.erase(it);
}

最常望記得就是那個delete,然後就造成Memory leak。
新的寫法就像這樣

1
2
3
4
auto it = std::find_if(objs.begin(), objs.end(), [](const unique_ptr<Object> &obj) -> bool { ...});

if (it != objs.end())
objs.erase(it);

被指向的Object resource會在某個時間點被歸還。

Get element from container

這邊可分成兩部份,單純瀏覽整個Container中的Elemeent,不對Container作任何動作。
或是當作資料結構,會改變Container本身的狀態。
先從第一種來說明,以往可能這麼作

1
2
3
4
5
for (auto it = objs.begin(); it != objs.end(); ++it)
{
Object *obj = *it;
// Do something
}

用了unique_ptr之後,只能這麼做了

1
2
3
4
5
for (auto it = objs.begin(); it != objs.end(); ++it)
{
Object *obj = (*it).get();
// Do something
}

如果要直接操作Container,像輕空Buffer queue的情形時,以前的作法

1
2
3
4
5
6
7
8
while (!objs.empty())
{
cout << "Move from vector" << endl;
Object *pObj = objs.front();
objs.pop_front();
// Do something
delete pObj;
}

不過由於unique_ptr不像auto_ptr擁有copy semantics,因此上面的程式碼要改成

1
2
3
4
5
6
7
while (!objs.empty())
{
cout << "Move from vector" << endl;
unique_ptr<Object> s = std::move(objs.front());
objs.pop_front();
// Do something
}

可以看到,跟Raw Pointer操作相差無幾,不過利用RAII技術減少Memory leak的發生。

這種檔案格式最近才流行起來,7zup可以直接解壓縮。
在Linux底下可以這麼作

1
2
$ xz *.tar.xz
$ tar xvf *.tar

chrono原先是來自於boost,現在進入C++11 Standard了。
鑽簡單的範例就是用來作高精密度的Timer使用。原先在Windows有QueryPerformanceCounter的函數,可以精密到nanoseconds,在Liunx/BSD很難找到類似的解法,不過C++11把這納入標準了,不必花太多功夫。
以下是一個範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <chrono>
#include <thread>
using namespace std;
void f()
{
this_thread::sleep_for(chrono::seconds(1));
}

int main()
{
auto t1 = chrono::high_resolution_clock::now();
f();
auto t2 = chrono::high_resolution_clock::now();
std::cout << "f() took "
<< chrono::duration_cast<chrono::seconds>(t2 - t1).count()
<< " seconds\n"
<< chrono::duration_cast<chrono::milliseconds>(t2 - t1).count()
<< " milliseconds\n"
<< chrono::duration_cast<chrono::nanoseconds>(t2 - t1).count()
<< " nanoseconds\n";
return 0;
}

更多的使用方法可以參考

最近遇到在GitHub frok出來的Branch改爛之後,想要捨棄掉自己的修改,重新跟上Upstream的狀態。
沒想到已經有人解答了
Clean up a fork and restart it from the upstream
GitHub page, section “What should I do if I’m in a bad situation”
就是以下四個指令的組合技

1
2
3
4
$ git fetch upstream
$ git checkout master
$ git reset --hard upstream/master
$ git push origin master --force

寫一下編譯跟安裝Boost該注意的事,目前先寫Linux下的情況,有時間日後再補上

Linux (Ubuntu 13.04)

1
2
3
$ apt-get install g++ g++-4.8 g++-4.8-multilib gcc-4.8-doc libstdc++6-4.8-dbg libstdc++-4.8-doc
$ cd boost_1_55_0
$ ./bootstrap.sh ## Generate bjam

關於bootstrap.sh的詳細用法可以打./boostrap.sh --help獲得,。如果不更改--prefix的話,預設的路徑就是/usr/local

1
$ ./bjam --build-dir=./tmp --stagedir=./build

關於bjam的常用使用說明可以參照,以下是常用參數

  • –build-dir=directory 將build過程的中間產物放置目錄底下,方便管理
  • –stagedir=directory 最後編譯完成的Library放置處
  • –variant=debug | release 可以選擇Release 或是 Debug模式
  • –link=static | shared 選擇build出來的事static library或是sshared library
  • –threading=single | multi 選擇Threading model
  • –runtime-link=static | shared 連結C/C++ Standard Library時,選擇Static library或是Shared library link
  • –with- 只編譯某Library,如 --with-regex
    ` –clean 清理中間產物
    編譯玩之後就可以安裝了
    1
    $ ./bjam install

在Stackoverflow看到,怕忘記寫下來。

如何對std::map使用 for-ranged syntax

原文在此。
簡單的說就是用auto去接reference。

1
2
3
for (auto& kv : myMap) { 
std::cout << kv.first << " has value " << kv.second << std::endl;
}

如何反向訪問container

原文在此。
最簡單的方法是使用Boost

1
2
3
4
#include <boost/range/adaptor/reversed.hpp>

for (auto i : boost::adaptors::reverse(x))
std::cout << i << '\n';

如果沒有boost的話也可以土法煉鋼,原文Paul的方法不能使用,選用下面Jive的方案

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
template<class Fwd>
struct Reverser_generic {
Fwd &fwd;
Reverser_generic(Fwd& fwd_) : fwd(fwd_) {}
typedef std::reverse_iterator<typename Fwd::iterator> reverse_iterator;
reverse_iterator begin() { return reverse_iterator(std::end(fwd)); }
reverse_iterator end() { return reverse_iterator(std::begin(fwd)); }
};

template<class Fwd >
struct Reverser_special{
Fwd &fwd;
Reverser_special(Fwd& fwd_) : fwd(fwd_) {}
auto begin() -> decltype(fwd.rbegin()){ return fwd.rbegin(); }
auto end() ->decltype(fwd.rbegin()) { return fwd.rend(); }
};

template<class Fwd>
auto reverse_impl(Fwd& fwd, long) -> decltype(Reverser_generic<Fwd>(fwd)){
return Reverser_generic<Fwd>(fwd);
}

template<class Fwd>
auto reverse_impl(Fwd& fwd, int)
-> decltype(fwd.rbegin(), Reverser_special<Fwd>(fwd))
{
return Reverser_special<Fwd>(fwd);
}

template<class Fwd>
auto reverse(Fwd&& fwd) -> decltype(reverse_impl(fwd, int(0))) {
static_assert(!(is_rvalue_reference<Fwd&&>::value),
"Cannot pass rvalue_reference to dj::reverse()");
return reverse_impl(fwd, int(0));
}

相信大家都有用過grep來找文字,不過這個ack是專為程式設計師開發的。
從官網下載並安裝

1
2
3
$ curl http://beyondgrep.com/ack-2.12-single-file > ack
$ sudo mv ack /usr/local/bin
$ sudo chmod 755 /usr/local/bin/ack

ack有以下這些特性

  • 預設就搜尋當前目錄
  • 預設搜尋所有子目錄
  • 忽略Metadata資料節,如.svn.git
  • 忽略Binary檔
  • 印出找到Pattern的行號
  • 能夠搜尋特定文件類型的檔案 (如Perl/C++)
  • Highlight搜尋結果
  • 支持Perl的Advance Regular Expression

幾個常用的功能

如何只搜尋eat而忽略掉feature和`eating

1
$ ack -w eat 

當Pattern中有特殊字元的時候,需要當作Literal chracter被匹配

1
$ ack -Q '$path/$'

放棄搜尋某些目錄

1
$ ack about --ignore-dir=downloads 

列出某些特定文件類型的檔案,列出擁有Pattern的檔案名稱

以Makefile為例,可能的檔名有 *.mk, makefile, Makefile
我們想知道這些檔案裡面哪些定義了CFLAGS,就能夠這樣作

1
$ ack --make -l CFLAG 

Highlight某些特定Pattern

1
$ tail -f /var/log/syslog | ack --passthru 192.168.1.10 

這裡的passthru是不管有沒有匹配到都會輸出。

Update

在網路上釉看到ag這套軟體。用途大同小異。多了一些ack沒有的功能。
可以參考Conquering the Command Line Chapter 2. Ack/Ag