0%

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

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來看程式碼了

忽略其他 3rd-party的部分, 編譯一個極簡版的caffe2
為了方便, 把步驟記錄起來

Dependency

– CMake
– Python3
– Numpy

下載程式碼

1
git clone --recursive https://github.com/caffe2/caffe2.git

接著進入Visual studio native tools command prompt進行編譯過程

Build Protoc

1
scripts\build_host_protoc.bat

warning都可以忽略, 可以產生可用的protoc

Build Caffe2

1
scripts\build_windows.bat

這樣就能產生caffe2 library了, 也可以打開caffe.sln來看

其實最麻煩的反而是’Dependecny`那塊, 需要手動安裝
目前的Caffe2只支援CPU Mode

雖然Concept還沒正式列入C++ Standard中,不過提早經歷過總是好事..
Concept像Interface,定義一個靜態Concept該有些什麼東西
雖然可以用Interface完成,不過有時候我們不需要Runtime Polymorphism
常見的做法是用template來做

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <string>
template <typename T>
void print(T obj)
{
std::cout << obj.c_str() << ", length: " << obj.length() << "\n";
}
int main()
{
print(std::string("123"));
print(123); // Compile error
}

這方法最大的問題就是error message很難看懂, 常常跟Template打交道就有感覺了

1
2
3
4
5
6
7
a.cpp: In instantiation of ‘void print(T) [with T = int]’:
a.cpp:23:11: required from here
a.cpp:18:19: error: request for member ‘c_str’ in ‘obj’, which is of non-class type ‘int’
std::cout << obj.c_str() << ", length: " << obj.length() << "\n";
~~~~^~~~~
a.cpp:18:51: error: request for member ‘length’ in ‘obj’, which is of non-class type ‘int’
std::cout << obj.c_str() << ", length: " << obj.length() << "\n";

如果用Concept表達的話會是這個樣子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string>
template <class T>
concept bool Printable() {
return requires(const T& obj) {
{ obj.c_str() }-> const char *;
{ obj.length() } -> size_t;
};
}
void print(Printable obj)
{
printf("%d: %s\n", obj.length(), obj.c_str());
}
int main()
{
print(std::string("123"));
print(123);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
a.cpp: In function ‘int main()’:
a.cpp:17:11: error: cannot call function ‘void print(auto:1) [with auto:1 = int]’
print(123);
^
a.cpp:10:6: note: constraints not satisfied
void print(Printable obj)
^~~~~
a.cpp:4:14: note: within ‘template<class T> concept bool Printable() [with T = int]’
concept bool Printable() {
^~~~~~~~~
a.cpp:4:14: note: with ‘const int& obj’
a.cpp:4:14: note: the required expression ‘obj.c_str()’ would be ill-formed
a.cpp:4:14: note: the required expression ‘obj.length()’ would be ill-formed

面的Error告訴我們123不如的類型是int, 而int不符合 Printable這個Concept的要求

Limitiation

Concept只是Template的一層Wrapper而已,所以這樣的程式碼也是能通過的

1
2
3
4
5
6
7
8
9
10
11
12
13
emplate <class T>
concept bool Printable() {
return requires(const T& obj) {
{ obj.c_str() }-> const char *;
{ obj.length() } -> size_t;
};
}
void print(Printable obj)
{
if (!obj.empty()) {
std::cout << obj.c_str() << ", length: " << obj.length() << "\n";
}
}

明明Printable中沒定義empty的觀念,不過程式還是編譯通過,這個時候只能多加留意了

雖然C++11之後沒對Pointer做任何加強,不過也沒縮減他的能力
Modern C++ 不鼓勵直接使用Raw Pointer,用了一堆Toolkit做取代方案
來分析一下Raw Pointer有哪些問題以及怎麼做比較好

The problem of raw pointer

Raw Pointer最大的問題是語義不夠強
拿以下兩個例子來說

1
2
int* produce();
void consume(int *);

dosomething傳回的指標需要釋放嘛?這問題除了查看文件或是看Sourece Code外別無他法。因此很容易誤用
同樣的問題,consume參數的Pointer需要在函數中釋放嘛?假設consume釋放了記憶體,不過caller傳進來的的參數不是透過allocated拿到的(stack array or something),然後城市就掛掉了
從這兩個範例來看,你不能從函數宣告知道指標該怎麼處理
其他的Memory Leak等問題就不詳述了,以下是我對Raw Pointer和Modern C++的一些見解

Reference

Reference不是什麼新東西,C++98就有了,不過這也是有效減少Pointer issue的方式之一
Reference和Pointer的差異就不詳述了,上面兩個範例可以用Reference表示

1
2
int produce();
void consume(int&);

這樣一看,對於原先的版本,關於記憶體該誰釋放這點就很清楚了

std::vector

萬一原先的函數是要回傳一個array,而非單一元素,同樣在函數宣告無法很好的表達出來
不過用vector就知道我需要回傳一個vector

1
2
std::vector<int> produce();
void consume(std::vector<int>&);

std::string_view

假設我們要處理的是一個char array

1
2
char* produce();
void consume(char *);

如果用std::string可以更好的表達語義

1
2
std::string produce();
void consume(std::string &);

由於頻繁的Memory allcation/deallcation會造成不小的開銷
假設你餵給consume的參數是一個const char pointer,會做以下的事情

  1. 隱性的建構一個std::string物件
  2. 呼叫std::string(const char*)建構式
  3. 執行consume
  4. 結束之後把物件釋放掉
    類似的問題也在produce出現,有時在produce的回傳值我們並不需要另一個物件
    在C++98之前我們通常都這麼做
    1
    std::string& produce();
    不過如果caller方沒寫好一點用都沒有
    1
    2
    std::string& obj = produce(); // (O)
    std::string obj = produce(); // (X)
    後者還是會建立個物件, 然後呼叫Copy Constructor。
    因此C++17之後將string_view列入STL,類似的實作已經出現在各大Library了
    1
    2
    std::string_view produce();
    void consume(std::string_view);
    這樣誤用的機會又更小了

    Smart Pointers

    相信都寫過類似這樣的程式碼
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void doSomething() {
    int *arr = new int[100];
    if (cond1) {
    delete [] ar;
    return;
    }
    // Do something
    if (cond2) {
    }
    // Do anotherthing
    return;
    }
    每次都需要在每個回傳路徑檢查是否記憶體正確釋放,當重夠很多次之後,整個程式碼被遺忘的機會更多,這個時候讓編譯器幫忙可以少很多事端
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void doSomething() {
    std::unique_ptr<int> arr(new int[100]);
    if (cond1) {
    return;
    }
    // Do something
    if (cond2) {
    }
    // Do anotherthing
    return;
    }
    Smart Pointer還有shared pointer和weak pointer,這裡就不細說了

std::optional

這是另外一個跟指標有關, 不過跟上面不太相同的問題
假設我們現在有個需求

  1. 搜尋一個陣列
  2. 如果找到符合條件的話, 回傳給Caller
  3. Caller使用這個符合條件的值做修改
    類似的程式碼可能長這樣
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int* findArray(int *arr, int size, int v)
    {
    for (int i = 0; i < size; i++)
    if (arr[i] == v) return arr + i;
    return NULL;
    }
    int *p = findArray(arr, size, v);
    if (p)
    *p = anotherValue;
    這個問題不能用Refernce解決,因為Reference不允許Dereference null,因此在之前的作法還是得退化至Pointer Solution
    不過有了std::optional之後,語義有所提昇
    上面的例子可以寫成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::optional<int&> findArray(std::vector<int> &arr, int v)
    {
    for (size_t i = 0; i < arr.size(); i++)
    if (arr[i] == v) return arr[i];
    return {};
    }
    std::option<int&> p = findArray(arr, v);
    if (p)
    *p = anotherValue;

Conclusion

雖然Raw Pointer威力強大,無所不能,但未了減少失控。做些房物措施無可厚非
用些Modern C++的技巧可以少犯不少錯誤,如果真的需要最佳化的時候,在把這些拿掉蛻化成Raw Pointer也不遲
先講究不傷身體,在講究效果..

前陣子沉迷於大唐雙龍傳,所以耍廢了一陣子,來紀錄一下新學到的觀念

How to deal error in C language

簡單直接的作法,定義error,然後把ReturnCode當作ErrorCode回傳

1
2
3
4
5
6
7
8
9
10
11
enum errors
{
SUCCESS = 0,
NOTFOUND,
};
int openFile(const char *filename, int *pfd) {
int fd = open(filename, , O_RDONLY);
if (fd == -1) return NOTFOUND;
*pfd = fd;
return SUCCESS;
}

不過這邊有個小問題,當兩個不同的Componet有相同的ErrorCode怎麼辦,假設LibA和LibB都有NOTFOUND的定義
通常的作法就是改名,然後用類似LibA_NOTFOUNDLibB_NOTFOUND來繞過

How to deal error in pre C++11

當然是用Exception來表示Error

1
2
3
4
5
6
int openFile(const char *filename) {
int fd = open(filename, , O_RDONLY);
if (fd == -1)
throw std::exception("File not fould");
return fd;
}

遮方式當然也有缺點,需要考慮Runtime overhead,有些Coding Style不鼓勵用Exception(例如Google)

std::error_code in C++11

C++11從Boost引進了error_code的觀念

1
2
3
4
5
6
7
8
9
10
11
12
13
int openFile(const char *filename, std::error_code &ec) {
int fd = open(filename, , O_RDONLY);
if (fd == -1)
ec = std::error_code(errno, std::system_category());
return fd;
}
std::error_code ec;
int fd = openFile(filePath, ec);
if (ec) {
std::cout << "Category: " << ec.category().name()
<< "Value: " << ec.value() << '\n'
<< "Message: " << ec.message() << '\n';
}

這邊作法類似於C語言的作法,不過不同的是引進了Category的觀念
於是可以自定義一個Category和自己的Error_code,解決了Conflict的問題
不過如果需要的話,當然也可以當Exception丟出去

1
2
int fd = openFile(filePath, ec);
if (ec) throw ec;

當你需要時才把Exception丟出

std::error_condtion

如果要對Error做條件處理該怎麼做,野引進了error_condtion的觀念

1
2
3
4
5
6
7
8
9
int fd = openFile(filePath, ec);
std::error_condition cond1(1, std::system_category());
std::error_condition cond2(2, std::system_category());
if (ec == cond1) {
}
else if (ec == cond2) {
}
else {
}

Custom your categlory and error_code

Stackoverflow看到如何自定義ErrorCode和Category的方式,記錄下來

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
#include <iostream>
using namespace std;
#include <system_error>
#include <cassert>

namespace mylib
{
namespace errc {

enum my_error
{
failed = 0
};

inline const char* error_message(int c)
{
static const char* err_msg[] =
{
"Failed",
};

assert(c < sizeof(err_msg) / sizeof(err_msg[0]));
return err_msg[c];
}

class my_error_category : public std::error_category
{
public:

my_error_category()
{ }

std::string message(int c) const
{
return error_message(c);
}

const char* name() const noexcept { return "My Error Category"; }

const static error_category& get()
{
const static my_error_category category_const;
return category_const;
}
};

inline std::error_code make_error_code(my_error e)
{
return std::error_code(static_cast<int>(e), my_error_category::get());
}

} // end namespace errc
} // end namespace mylib

namespace std {

template<>
struct is_error_code_enum<mylib::errc::my_error>
: std::true_type
{ };

} // end namespace std

int main()
{
std::error_code ec1 = mylib::errc::make_error_code(mylib::errc::failed); // works
std::error_code ec2 = mylib::errc::failed; // works
bool result = (ec2 == mylib::errc::failed); // works

std::cout << ec1 << std::endl;
}

前情提要

看云是個非常不錯的文件共享平台, 有提供mobi, epub, pdf不同的格式放至電子閱讀器上看, 不過人生最困難的就是那個but, 雖然可以在E-INK上看, 不過由於CSS Layout的關係, 在螢幕上看的效果跟在E-INK上相差太大
我就是不想一直盯著螢幕, 所以只好自力救濟, 寫出一個Converter

Golang practice

由於主要語言是C/C++, 之前有看過golang的語法而未正式動手
這次特別練習了一下, 不難
運用到的Library只有goquery
將網頁抓下來, 去除掉不必要的部分, 再利用pandoc將html轉成markdown格式

Github Repo

go_epub

從最簡單的情況說起

煤捕捉任何狀態的lambda可以直接轉化成普通的C function型態

1
2
3
4
5
6
7
8
typedef void(*callback)();
void testFunc(callback cb)
{
cb();
}
testFunc([] {
cout << "Lambda function" << endl;
});

帶狀態的情形

通常callback還會帶一個void *參數,可以讓你上下其手

1
2
3
4
5
6
7
8
9
10
typedef void(*callback)(void *);
void testFunc(callback cb, void *user_data)
{
cb(user_data);
}
int v = 123;
testFunc([](void *user_data) {
int *v = static_cast<int *>(user_data);
cout << "Lambda function " << *v<< endl;
}, &v);

當需要更多上下文時,需要自行定義structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// local variables
int x = 0;
float y = 1;

// locally defined uncopyable, unmovable type
struct MtEverest
{
MtEverest() = default;
MtEverest(const MtEverest& that) = delete; // no copy
MtEverest(const MtEverest&& that) = delete; // no move
} mt_everest;

// create "user-data" payload
auto payload = std::tie(x, y, mt_everest);
testFunc([](void *user_data) {
auto& payload_tup = *reinterpret_cast<decltype(payload)*>(user_data);
auto& xx = std::get<0>(payload_tup);
auto& yy = std::get<1>(payload_tup);
auto& me = std::get<2>(payload_tup);
}, &payload);

更高明的解法

當C API沒辦法有void *的指標讓你上下其手,或是packing/unpacking的程式碼讓人不耐
可以考慮以下這個解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename Lambda>
static auto lambdacb(Lambda &&l)
{
thread_local auto* p = &l; // initial assignment, allows using auto
p = &l;
return []() { return (*p)(); };
}

typedef void(*callback)();
void testFunc(callback cb)
{
cb();
}
// local variables
int x = 0;
float y = 1;

testFunc(lambdacb([&x, &y] {
cout << x << " " << y << endl;
}));

Reference

Lambda Magic
Lambda Callbacks
Technical Debt

看到這篇有感而發,現實中常見的問題之一,為了方便說明,把原先的問題簡化,求鄰近元素後者比前面大的數對

最初的方法

原先我只會用這種方法

1
2
3
4
5
6
7
int calc(const std::vector<int> &v)
{
int count = 0;
for (size_t i = 0; i < v.size() - 1; i++)
if (v[i + 1] > v[i]) count++;
return count;
}

利用vector來作,實在不高明

Better Solution

參考上面blog的作法

1
2
3
4
5
6
7
8
9
template <typename T>
int calc(const T& v)
{
int count = 0;
for (auto it1 = v.cbegin(), it2 = v.cend(); it1 != v.cend(); it2 = it1, ++it1)
if (it2 != v.end())
if (*it1 > *it2) count++;
return count;
}

好一點了,不限定要是vector,不過還是要思考一下it1和it2的關聯性

Range-V3 Solution

可能成為下一代STL的Range-v3,其作法就類似FP中的pipeline的方式處理,隱藏了iterator的存在

1
2
3
4
5
6
7
template <typename T>
int calc(const T& v)
{
using namespace ranges;
auto larger = [](auto front, auto back) { return front > back; };
return distance(v | view::adjacent_remove_if(larger)) - 1;
}

Range-V3 Solution Ver 2, Sliding Window

Range-v3最近加入了Sliding Window的觀念,比起上面的方式更加通用,不過還是不知道怎麼把他轉成Ranges轉成Tuple,只好寫成這樣

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
int calc(const T& v)
{
using namespace ranges;
return count_if(v | view::sliding(2), [](const auto &v) {
auto begin = ranges::begin(v);
auto front = *begin++;
auto back = *begin++;
return front < back;
});
}

Conclusion

抽象的程度越高,Debug的難度也自然越高,看著gcc或是clang吐出來的compilation error真是一個頭兩個大,尤其是跟Range-v3扯上關係

Reference

range-v3
Super expressive code by Raising Levels of Abstraction
Ranges: the STL to the Next Level

最近摸索出來的心得,先來看看傳統的 Composite design pattern要怎麼作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <vector>
#include <memory>
using namespace std;

class Component
{
public:
virtual ~Component() = default;
};

class Leaf : public Component
{
};

class Composite : public Component
{
vector<unique_ptr<Component>> children;
public:
void add(Component *ele)
{
children.push_back(std::unique_ptr<Component>(ele));
}
};

這個方法不差,不過還是有幾點可以改良的
– 每個Subtype都必須繼承Component,就算沒有任何is-a的關聯性還是必須這麼作
– 當要Clone一份物件出來的話,需要另外一個Prototype Pattern
例如

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
class Component
{
public:
virtual ~Component() = default;
virtual Component* clone() = 0;
};

class Leaf : public Component
{
public:
Component* clone() override { return new Leaf(); }
};

class Composite : public Component
{
vector<unique_ptr<Component>> children;
public:
void add(Component *ele)
{
children.push_back(std::unique_ptr<Component>(ele));
}
Component* clone() override {
Composite* composite = new Composite();
for (const auto &child : children)
composite->children.push_back(
std::unique_ptr<Component>(child->clone()));
return composite;
}
};

每個都這麼作實在很醜,並且又容易錯

Solution based on std::variant

使用std::variant可以解決上面的問題,乾淨俐落

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Leaf;
class Composite;
using CompositeVar = variant<Leaf, Composite>;
class Leaf final
{
public:
Leaf() = default;
Leaf(const Leaf&) = default;
};

class Composite final
{
vector<CompositeVar> children;
public:
Composite() = default;
Composite(const Composite&) = default;
void add(const CompositeVar &ele)
{
children.push_back(ele);
}
};

不過這也不是萬用解,當你的CompositeVar 中的type數目是有限的,可以採用這個方式
不然就是每加入一種Type,就必須重新編譯一次,反而沒有舊版的彈性