0%

過年前有個工作需求,需要跨平台的AES硬體加速功能,研究了一下幾種方案
– Openssl (增加相依性,加上編譯時需要設定改來改去)
– Runtime JIT (先判斷CPU種類,然後根據CPU類型生成組合語言,小小的玩意哪需要玩這麼大)
libkcapi – Linux Kernel Crypto API User Space Interface Library
覺得最後一種方式不錯,不過我只需要AES,其他地方可以拿掉
因此對這Project進行二次加工,產生了KCAES這個專案
不過在進行AES CBC運算時,發現長度超過64K就會報錯,只好對超過64K的Block進行二次加工
不需要其他的相依性,只要把檔案放進自己的專案,加入編譯即可

去英國晃了一圈,回來還是寫點東西,免得生疏了

static_assert

沒什麼好說的,就是static_assert改成允許單參數,直接看程式碼

Before C++17

1
static_assert(sizeof(short) == 2, "sizeof(short) == 2")

After C++17

1
static_assert(sizeof(short) == 2)

Inline Variables

對Header-Only library特別有用,不過我不喜歡Header-Only library
原先如果要定義一個變數,要在header宣告,在source code裡面定義
現在可以直接寫在header裡了

Before C++17:

1
2
3
4
5
// foo.h
extern int foo;

// foo.cpp
int foo = 10;

After C++17:

1
2
// foo.h
inline int foo = 10;

constexpr labmda

原先C++14辦不到,C++17允許的能力

1
2
3
4
5
constexpr int Func(int x)
{
auto f = [x]() { return x * x; };
return x + f();
}

不過我還沒想到這東西可以做啥

capture [*this]

原本我還搞不懂capture [this]capture [*this]有什麼不同,自己寫了一個範例之後搞懂了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <string>
#include <iostream>
struct Obj {
Obj() = default;
Obj(const Obj&) { std::cout << "Copy Constructor\n"; }
void f() {}
void g() {}
void h() {}
void func() {
auto lambda1 = [this]() mutable { f(); };
auto lambda2 = [self = *this]() mutable { self.g(); };
auto lambda3 = [*this]() mutable { h(); };
lambda1();
lambda2();
lambda3();
}
};
int main()
{
Obj o;
o.func();
}

capture [*this]相當於上面的[self = *this],會將原有的物件複製一份
[this]不會

More Attributes

C++11引進了Attribute,在C++17增加了更多attributes,如[[fallthrough]]等
就是把GCC/VC的attribute標準化,不多做解釋了

Rest

至於STL的加強就不特別寫了, Guaranteed Copy Elision可能要另外寫
就先寫到這了

Reference

cpp17_in_TTs
C++17

if constexpr

以往我們可能寫出類似這樣的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <string>
template <typename T>
int func(T) {
return -1;
}
template <>
int func(std::string v)
{
return 12;
}
template <>
int func(int)
{
return 34;
}
int main()
{
return func(std::string("123"));
}

如今我們可以寫成這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <string>
template <typename T>
int func(T) {
if constexpr (std::is_same_v<T, std::string>) {
return 12;
} else if constexpr(std::is_same_v<T, int>) {
return 34;
} else {
return -1;
}
}
int main()
{
return func(std::string("123"));
}

省去了很多的冗於
如果配合variant來使用,程式碼可以寫成這樣

1
2
3
4
5
6
7
8
9
10
11
12
#include <string>
#include <variant>
using unionType = std::variant<std::string, int>;
int main()
{
unionType v = 3;
return std::visit([](const auto &v) {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, int>) return 12;
else return 34;
}, v);
}

Class Deduction guide

在c++17之前,寫了很多這樣子的程式碼

1
2
std::mutex m;
std::lock_guard<std::mutex> lock(m);

為什麼函數可以推導出型別,而類型不行,於是C++17放寬了這條件

1
2
std::lock_guard lock(m);
std::vector v{1, 2, 3};

當然,也是有explicit class deduction guide的,請參考reference

template <auto>

目前看起來沒什麼用的feature
未用c++17前程式碼長這樣

1
2
3
4
5
template <typename T, T v>
struct integral_constant {
static constexpr T value = v;
};
integral_constant<int, 1024>::value;

用了C++17後

1
2
3
4
5
template <auto v>
struct integral_constant {
static constexpr auto value = v;
};
integral_constant<1024>::value;

nested namespace

早該有的東西, 結果拖到這麼後面才加進來

1
2
3
4
5
namespace X {
namespace Y {
struct Foo;
}
}

現在可以寫成

1
2
3
namespace X::Y {
struct Foo;
}

Fold expression

以加法為範例
在c++17之前的寫法

1
2
3
4
5
6
7
8
template<typename T>
T sum(T v) {
return v;
}
template<typename T, typename... Args>
T sum(T first, Args... args) {
return first + sum(args...);
}

把sum寫成兩部分,雖然不是不行,不過總覺得被切割加重學習負擔
用上if constexpr

1
2
3
4
5
6
7
8
template<typename T, typename... Args>
T sum(T first, Args... args) {
if constexpr (sizeof...(Args) == 0) {
return first;
} else {
return first + sum(args...);
}
}

好一點了,用上Fold expression會變成怎樣

1
2
3
4
template<typename ...Args>
auto sum(Args&&... args) {
return (args + ...); // OK
}

if constexpr方法比是更精簡了一點,不過多了一堆語法規則,實在不太划算

Reference

A Tour of C++ 17: If Constexpr
C++17中的deduction guide
C++17 Fold Expressions

對於C++17特性的文章已經有很多了,歸納自己的想法寫成幾篇文章

if / switch init

覺得很有用的特性之一
在沒有C++17之前,程式碼大概長這樣

1
2
3
4
5
6
7
8
void test() {
Foo *ptr = get_foo();
if (!ptr)
do_something();
else
ptr->do_something();
more_code();
}

在C++17之後

1
2
3
4
5
6
7
void test() {
if (Foo *ptr = get_foo(); !ptr)
do_something();
else
ptr->do_something();
more_code();
}

Structure Binding

在沒有Structure Binding時,程式碼大概長這樣

1
2
3
4
std::tuple<int, double> stuff();
auto tup = stuff();
int i = std::get<0>(tup);
double d = std::get<1>(tup);

或者是

1
2
3
int i;
double d;
std::tie(i, d) = tup;

不過都比不上

1
auto [i, d] = stuff();

傳統的

1
errcode doSomething(obj *out_value);

之後會變成

1
2
std::tuple<obj, errcode> doSomething();
auto [obj, err] = doSomething();

有點golang的感覺

不過看看以下程式碼

1
2
3
4
5
6
7
8
9
10
11
struct Foo
{
int x = 0;
std::string str = "world";
~Foo() { std::cout << str << "\n"; }
};
{
auto [x, s] = Foo();
std::cout << "Hello ";
s = "Goodbye";
}

結果出乎意料,不是大家所想的copy by value,而是copy by reference
Compiler大概做的是像這樣

1
2
3
4
5
6
int main()
{
auto temp = Foo();
std::cout << "Hello ";
temp.str = "Goodbye";
}

還有下面這個範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct X { int i = 0; };
X makeX();
int main()
{

X x;

auto [ b ] = makeX();
b++;
auto const [ c ] = makeX();
c++;
auto & [ d ] = makeX();
d++;
auto & [ e ] = x;
e++;
auto const & [ f ] = makeX();
f++;
}

c++ 編譯不過,因為這是bind to const reference
auto & [d] = makeX() 編譯不過,因為left reference不能bind to right value
f++ 編譯不過,理由同第一條

Reference

cpp17_in_TTs

看看以下程式有什麼不一樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
struct Obj {
Obj() { cout << "Default Constructor\n"; }
~Obj() { cout << "Destructor\n"; }
Obj(const Obj &) { cout << "Copy Constructor\n"; }
Obj(Obj &&) { cout << "Move Constructor\n"; }
};
template <typename T>
auto outer(T && obj) {
return std::forward<T>(obj);
}

int main()
{
auto& obj = outer(Obj());
return 0;
}

1
2
3
4
template <typename T>
decltype(auto) outer(T && obj) {
return std::forward<T>(obj);
}

差異就在於auto和decltype的用途不太一樣,auto會去掉reference,而decltype(auto)不會

C++11’s solution

C++11也可以達到decltype(auto)的方式,不過寫法比較繁瑣

1
2
3
4
template <typename T>
auto outer(T&& obj) -> decltype(std::forward<T>(obj)) {
return std::forward<T>(obj);
}

cgroup是linux用來限制program使用Computer resource的一種方法
也是Docker的基礎

首先先安裝cgroup

1
2
3
4
5
6
7
8
$ sudo apt install cgroup-bin
```
寫個程式

``` python
count = 0
while True:
count = count + 1

不用看也知道他絕對吃滿cpu resource

所以該怎麼限制,例如只讓他吃20%的CPU

先建立cgroup的群組

1
2
3
4
5
$ cd /sys/fs/cgroup/cpu # 管理CPU資源的地方
$ sudo mkdir calm # 建立一個目錄
$ ls calm # 自動產生和cpu有關的規則
cgroup.clone_children cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.stat tasks
cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.shares notify_on_release

接著把我們程式限制的規則加入群組 以下動作需要root權限,sudo無法執行

1
2
$ echo 20000 > calm/cpu.cfs_quota_us # 預設值是100000,20000正好是20%
$ echo 3255 > calm/tasks # 3255 是程式的 PID

接著我們就能看到程式CPU使用率就只剩20%了

Another method

也可以用自定義規則的方式

1
2
3
4
$ sudo cgcreate -g cpu:calm # 一樣是建立 cpu calm
$ sudo cgset -r cpu.cfs_quota_us=20000 calm # 跟上面差不多
$ sudo cgget calm # 列出calm的所有規則
$ sudo cgdelete calm

如果要執行程式的話

1
$ sudo cgexec -g cpu:calm python busy.py

跟上面有同樣的效果

之前大概有寫過C++11 error_code的文章,不過覺得不夠清楚
這篇當作補充

定義Error

為了示範,僅定義幾個錯誤

1
2
3
4
5
#include <system_error>
enum class MyErrC {
FileNotFound = 1,
InvalidArgs
};

定義Error Category

定義Error的Domain,需要繼承自std::error_category,範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyErrCategory : std::error_category {
public:
const char* name() const noexcept override { return "MyErrCategory"; }
std::string message(int ev) const {
switch (static_cast<MyErrC>(ev)) {
case MyErrC::FileNotFound:
return "File not found";
case MyErrC::InvalidArgs:
return "Invalid Args";
default:
return "(unrecognized error)";
}
}
};

make_error_code

定義完Error Class和Category Class之後,就可以寫出make_error_code

1
2
3
4
5
std::error_code make_error_code(MyErrC e)
{
static const MyErrCategory category{};
return { static_cast<int>(e), category };
}

helper structure

不過就算這樣還是有點麻煩,我們希望寫出這樣的程式碼

1
std:error_code err = MyErrC::FileNotFound;

因此需要一個helper structure

1
2
3
4
5
namespace std
{
template <>
struct is_error_code_enum<MyErrC> : true_type {};
}

因此上面那行程式碼就能通過編譯了

Reference

Your own error code

自從C++11有了auto和decltype之後,整個coding style有了很大的改變
現在我們有一個字串統計的map,該怎麼走訪這個map才好

1
map<string, int> wordCount;

pre-C++11

沒別招了

1
2
3
4
for (map<string, int>::iterator it = wordCount.begin();
it != wordCount.end(); ++it) {
// do something
}

雖然可以用typename簡化map<string, int>::iterator不過大同小異,看起來也不怎麼美觀

C++11

auto被賦予了新生命,於是可以寫出這樣的程式碼

1
2
3
for (auto &p : wordCount) {
// do something
}

C++17引進了structure binding進一步簡化

C++17

1
2
3
for (auto &[word, count] : wordCount) {
// do something
}

不管從哪個角度看,切割字串都不是件簡單的工作
即便是簡單的字串格式

The simplest Example

先來個C語言處理方式

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
char str[] = "Tom 42";
int value;
char name[30];
sscanf(str, "%s %d", name, &value);
printf("%s %d\n", name, value);
}

C++的版本

1
2
3
4
5
6
7
8
9
10
11
12
#include <sstream>
#include <iostream>
int main()
{
std::string str = "Tom 42";
int value;
std::string name;
std::stringstream ss;
ss << str;
ss >> name >> value;
std::cout << name << " " << value << "\n";
}

看起來相差無幾,也沒需要動用到其他武器的地方
不過如果情況更複雜該怎麼辦

More Complex issue

例如輸入的字串是Tom: 42
如果什麼都不改的話,name會得到Tom:,不是我們想要的
如果改C語言的版本Parse string的字串

1
sscanf(str, "%s: %d", name, &value);

結果也不是我們想要的
看看C++的版本,不改的話結果一樣
如果改成這樣

1
2
3
int value;
std::string name, unused;
ss >> name >> unused >> value;

很顯然地也不對

Strtok

如果不動用重型武器,這是我唯一想得出來的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char str[] = "Tom: 42", *p = strtok(str, ":");
int value;
char name[30];
strcpy(name, p);
p = strtok(NULL, ":");
value = atoi(p);
printf("%s %d\n", name, value);
}

雖然解決了問題,不過程式碼支離破碎,維護起來也是麻煩透頂
萬一字串變成 Tom: 42, 123該怎麼辦

Boost Spirit

這世上不缺聰明人,想到了優雅到爆炸的作法
在C++內嵌DSL Parser解決問題,而使用的就是Boost Spirit
不過要說缺點的話,出錯Debug很麻煩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
int main()
{
std::string str = "Tom: 42";
typedef std::string::const_iterator It;
It iter = str.begin(), end = str.end();
int value;
std::string name;
bool r = qi::phrase_parse(iter, end,
(*(qi::char_ - ':') >> ":" >> qi::int_), qi::ascii::blank,
name, value);

if (r && iter == end) {
std::cout << "Parsing succeeded\n";
std::cout << name << " " << value << "\n";
}
else {
std::cout << "Parsing failed\n";
std::cout << "stopped at: \"" << std::string(iter, end) << "\"\n";
}
}

就如同上面說的如果要修改字串成Tom: 42, 123
我們可以將Parser寫成

1
2
3
bool r = qi::phrase_parse(iter, end, 
(*(qi::char_ - ':') >> ":" >> qi::int_ >> "," >> qi::int_), qi::ascii::blank,
name, value, value1);

高明的不得了

Rule & Grammer

當要Parse的字串越來越複雜,就可以自己定義Grammer和Rule了

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
#include <string>
#include <boost/tuple/tuple.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/boost_tuple.hpp>
using MyTuple = boost::tuple<std::string, int, int>;
namespace qi = boost::spirit::qi;
template <typename Iterator, typename Skipper = qi::ascii::blank_type>
struct StringGrammar : qi::grammar <Iterator, MyTuple(), Skipper> {
StringGrammar() : StringGrammar::base_type(parser, "StringParser Grammar") {
name = *(qi::char_ - ':');
value = qi::int_;

parser = name >> ":" >> value >> "," >> value;
}
private:
qi::rule<Iterator, MyTuple(), Skipper> parser;
// lexemes
qi::rule<Iterator, std::string(), Skipper> name;
qi::rule<Iterator, int(), Skipper> value;
};
int main()
{
std::string str = "Tom: 42, 123";
typedef std::string::const_iterator It;
StringGrammar<It> grammer;
It iter = str.begin(), end = str.end();
MyTuple v;
bool r = qi::phrase_parse(iter, end,
grammer, qi::ascii::blank,
v);

if (r && iter == end) {
std::cout << "Parsing succeeded\n";
std::cout << boost::get<0>(v) << " " << boost::get<1>(v) << " " << boost::get<2>(v) << "\n";
}
else {
std::cout << "Parsing failed\n";
std::cout << "stopped at: \"" << std::string(iter, end) << "\"\n";
}
}

經過測試,如果省略後面Skipper的模板定義,整個Grammer不會正常運作

Reference

Home of The Boost.Spirit Library
Parsing with Spirit Qi
spirit.Qi in boost

最近都在猛K機器學習, 有點荒廢了寫作
終於有時間可以寫點東西, 常常有在網路上看程式碼的需求,
於是就有新的工具誕生了

Build Woboq CodeBrowser

1
2
3
4
5
$ sudo apt install clang llvm libclang-dev
$ git clone https://github.com/woboq/woboq_codebrowser
$ cd woboq_codebrowser
$ cmake . -DLLVM_CONFIG_EXECUTABLE=/usr/bin/llvm-config -DCMAKE_BUILD_TYPE=Release
$ make -j 4

Take an example

1
2
3
4
5
6
7
8
9
10
$ git clone https://github.com/basiliscos/cpp-bredis
$ cd cpp-bredis
$ OUTPUTDIRECTORY=~/public_html/codebrowser
$ DATADIRECTORY=$OUTPUTDIRECTORY/../data
$ BUILDIRECTORY=$PWD
$ VERSION=`git describe --always --tags`
$ cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
$ ~/woboq_codebrowser/generator/codebrowser_generator -b $BUILDIRECTORY -a -o $OUTPUTDIRECTORY -p codebrowser:$BUILDIRECTORY:$VERSION
$ ~/woboq_codebrowser/indexgenerator/codebrowser_indexgenerator $OUTPUTDIRECTORY
$ cp -rv ~/woboq_codebrowser/data $DATADIRECTORY

值得注意的是, 這邊用cmake的out of build不起作用
所以就只能在root directory放在一起

之後就可以直接用Browser來看程式碼了