2018年10月28日 星期日

[Effective C++] Prevent exceptions from leaving destructors



#include <random>
#include <iostream>
#include <vector>
#include <sstream>

//#define BETTER_SOLUTION
//#define OK_SOLUTION

class Resource {
public:
 Resource() : pResource(new int(count++)) {}
 ~Resource() {}

 static Resource create() {
  Resource *rsc = new Resource();
 }

 void destroy() {
  std::random_device rd;
  std::default_random_engine dre(rd());
  std::uniform_real_distribution<> urd(0, 1);

  if (urd(rd) >= 0.95) {
   std::ostringstream oStream;
   oStream << "Exception from dtor. Resource " << *pResource << " leaked.";
   throw std::runtime_error(oStream.str().c_str());
  }

  std::cout << "Release Resource " << *pResource << std::endl;
  delete pResource;
 }

 int getResource() const { return *pResource; }

private:
 int *pResource;
 static int count;
};

int Resource::count = 0;

class ResourceHolder {
public:
 ResourceHolder() {
#ifdef BETTER_SOLUTION
  released = false;
#endif
 }
 ~ResourceHolder() {
#if defined(BETTER_SOLUTION)
  if (!released) {
   try {
    // Release resource if the client didn't
    pResource.destroy();
   } catch (std::exception &e) {
    // If closing fails, log that and terminate or swallow
    std::cout << "Holder cought a failure: "<< e.what() << std::endl;
    // Optional.
    // std::abort();
   }
  }
#elif defined(OK_SOLUTION)
  try {
   pResource.destroy();
  } catch (std::exception &e) {
   // Make a log that something failed.
   std::cout << "Holder cought a failure: "<< e.what() << std::endl;

   // Optional.
   // std::abort();
  }
#else
  pResource.destroy();
#endif
 }

#ifdef BETTER_SOLUTION
 void release() {// For client code
  pResource.destroy();
  released = true;
 }
#endif

 int resource() const { return pResource.getResource(); }

 friend std::ostream& operator <<(std::ostream &, const ResourceHolder&);

private:
 Resource pResource;
#ifdef BETTER_SOLUTION
 bool released;
#endif
};

std::ostream& operator <<(std::ostream &os, const ResourceHolder& w)
{
 os << "Do something on Resource " << w.resource();

 return os;
}

void createResourceAndTestLeave()
{
 std::vector<ResourceHolder> v(50);

 for (const auto& w : v) {
  std::cout << w << std::endl;
 }

#ifdef BETTER_SOLUTION
 for (auto& w : v) {
  try {
   w.release();
  } catch (std::exception &e) {
   std::cout << "Client caught exception: " << e.what() << std::endl;
  }
 }
#endif
}

int main()
{
 createResourceAndTestLeave();
}



討論

C++ 並未限制 dtor 丟出例外,但是在 dtor 丟出例外有機會造成 resource leak (沒有接此例外的話會直接離開 dtor)。



std::vector<ResourceHolder> v(50);

Effective C++ 書上提到,dtor 在處理這50個元件時,丟出一個例外,程式會繼續跑。若再丟出一個,程式執行會終止或 undefined behavior。

實際使用 Visual Studio 2013 與 g++測試,丟出一個 exception 未接,程式就會 abort。


在討論書本上的解法前,先想想...若不管此例外,讓此例外往上 propagate,要在哪邊接此例外是一個大問題。

直覺上,考慮每次有物件被生成與銷毀後,就於使用此物件的函式/scope 外,接此例外。例如,在 createResourceAndTestLeave(); 外面加上 try&catch。使用 Visual Studio 2013 實測,第一次例外發生時,函式未等到剩餘的 ResourceHolder 銷毀就會離開,並且由 catch 接到例外。

由以上觀察,此作法實務上並不可行,一樣會 resource leak。

解法

[法一] 在 dtor 內 catch 例外,並呼叫 abort
[法二] 在 dtor 內 catch 例外,並且吞下例外
ResourceHolder::~ResourceHolder() {
 try {
  pResource.destroy();
 }
 catch (std::exception &e) {
  // Make a log that something failed.
  std::cout << "Holder cought a failure: " << e.what() << std::endl;

  // Optional.
  // std::abort();
 }
}


[法三] 提供一個介面給 client code,讓 client code 自己呼叫釋放資源的函式,並且有機會自己處理此例外。除此之外, dtor 還是會檢查 client code 有無釋放資源,若沒有,再走[法一]或[法二]
ResourceHolder::~ResourceHolder() {
 if (!released) {
  try {
   // Release resource if the client didn't
   pResource.destroy();
  }
  catch (std::exception &e) {
   // If closing fails, log that and terminate or swallow
   std::cout << "Holder cought a failure: " << e.what() << std::endl;
   // Optional.
   // std::abort();
  }
 }
}
void ResourceHolder::release() {// For client code
 pResource.destroy();
 released = true;
}





2018年10月27日 星期六

東南亞三國 - 越南、柬埔寨、泰國 - 18 日自助遊簡易行程

總行程表


Day
1(飛機) 台北 -> 越南峴港 - 市區
2峴港 - 五行山峴港 - 巴拿山峴港 - 市區
3(巴士) 峴港 -> 會安會安 - 古城區會安 - 夜市
4會安 - 離島浮潛會安 - 夜市
5(飛機) 峴港 -> 胡志明市(飛機) 誤點胡志明市 - 范五老街
6胡志明市 - 大學生市區遊胡志明市 - 中國城區胡志明市 - 閒晃
7胡志明市 - 湄公河一日遊胡志明市 - 閒晃
8胡志明市 - 古芝地道一日遊胡志明市 - 閒晃
9(跨國巴士) 胡志明市 -> 柬埔寨金邊 - 市區、夜市
10金邊 - 殺戮戰場金邊 - 吐斯廉屠殺博物館、市區金邊 - 夜市
(夜巴) 金邊 -> 暹粒
11暹粒 - 大小吳哥窟暹粒 - 夜市
12暹粒 - 吳哥外圍暹粒 - 夜市
13暹粒 - 吳哥國家博物館暹粒 - 吳哥巴肯山日落暹粒 - 夜市
14(跨國巴士) 暹粒 -> 泰國曼谷 - 考山路夜市
15曼谷 - 閒晃曼谷 - 閒晃曼谷 - 閒晃
(夜巴) 曼谷 -> 清邁
16清邁 - 古城區清邁 - 古城區清邁 - 夜市
17清邁 - 飛鼠遊樂行程清邁 - 耍廢清邁 - 夜間動物園
18(飛機) 清邁 -> 台灣



行前準備

  • 簽證
    • 越南落地簽(事先註冊落地簽申請並可事先填好必要表單Entry and Exit Form)
    • 柬埔寨不用準備簽證(付錢給跨國巴士車長 35 美金處理到好)
    • 泰國落地簽(有點太貴了,先申請比較好)
  • 機票
    • 台北往越南
  • 旅遊書
  • 一段很長、很隨性的假
  • 一雙可以淋雨、好乾又好走的鞋(若遇到東南亞雨季)
  • 美金
    • 各國落地簽費用
    • 到當地兌換貨幣(柬埔寨用美金不用兌換)
    • 可先在桃園機場換少量的當地貨幣,以備不時之需

行程中

  • 詢問當地住宿
    • 跨國交通資訊(飛機或跨國巴士資訊)
    • 一日遊或半日遊資訊

交通銜接問與答

Q: 會安往胡志明市交通?

國內飛機 - Skyscanner

Q: 會安當地交通資訊?

翻攝至當地住宿

Q: 越南胡志明市前往柬埔寨金邊交通方式?

跨國巴士,總時間 9:00 ~ 17:00

直接請住宿幫忙訂跨國巴士,當天出發住宿載我到巴士公司


大巴,很舒服


車長來收柬埔寨入境處理費 $35 美金


開到柬蒲寨邊境要先下車入境,等超久,莫急莫慌


出境 ; 再次上車。出境處有 Sim 卡可以買

Q: 金邊前往暹粒(吳哥窟)交通方式?

跟住宿訂夜巴 -> Giant Ibis,有臥鋪,睡起來就到了

Q: 吳哥前往曼谷交通方式?

跟住宿訂跨國巴士,總時間 8:00 ~ 18:00
有點擠,座位不大(可以找好一點的跨國巴士)


中間需要下車出境柬埔寨海關,入境泰國海關
過程中要走到最後一張圖的位置,然後過橋,入關


順利出泰國海關


旅途中

Q: 曼谷前往清邁交通方式?

跟住宿訂夜巴或是找考山路上的旅行機構定巴士或火車票
巴士太小了,又沒臥鋪不好睡
建議事先定好高檔一點的巴士或火車

--
最後,整個行程只有事先看書,大概有個概念要踩哪些點。接著定好去程機票與第一天住宿,剩下的住宿、交通、行程都是到了當地在住宿看或問住宿的接待員,一整個彈性。

Cheers!

2018年10月25日 星期四

C++ 學習筆記:async, future

Example:


  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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#include <future>
#include <thread>
#include <chrono>
#include <random>
#include <iostream>
#include <exception>
#include <list>

int randomSleepAndOutput(char c)
{
    // random-number generator (use c as seed to get different sequences)
    std::default_random_engine dre(c);
    std::uniform_int_distribution<int> id(10, 1000);

    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(id(dre)));
        std::cout.put(c).flush();
    }

    return c;
}

void parallelExecution()
{
    std::cout << "Starting lambda1 in background and lambda2 in foreground:" << std::endl;

    auto lambda1 = []{ return randomSleepAndOutput('b'); };
    auto lambda2 = []{ return randomSleepAndOutput('f'); };

    // Execute callable object 1 asynchronously (now or later or never):
    std::future<int> result1(std::async(lambda1));

    // Execute callable object 2 synchronously (here and now)
    // If callable 1 is started, now we have a parallel execution
    int result2 = lambda2();

    // Wait for lambda1 to finish and add its result to result2
    int result = result1.get() + result2;

    std::cout << "\nlambda1() + lambda2() = " << result << std::endl;
}

void lazyEvaluation()
{
    auto lambda1 = []{ return randomSleepAndOutput('.'); };
    auto lambda2 = []{ return randomSleepAndOutput('+'); };

    std::future<int> result1(std::async(std::launch::deferred, lambda1));
    std::future<int> result2(std::async(std::launch::deferred, lambda2));

    // Do something heavy and then evaluation
    std::default_random_engine dre(12345);
    std::uniform_int_distribution<int> id(1, 1000);
    auto ms = id(dre);
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));

    // Lazy evaluation
    auto result = ms % 2 == 0 ? result1.get() : result2.get();
}

void throwExceptionTask()
{
    std::list<int> bigList;

    while (true) {
        for (int i = 0; i < 1000000; ++i) {
            bigList.push_back(i);
        }
        std::cout.put('.').flush();
    }
}

void parallelExecutionWithException()
{
    std::cout << "Starting 2 tasks" << std::endl;
    std::cout << " - task1: process endless loop of memory consumption" << std::endl;
    std::cout << " - task2: wait for <return> and then for throwExceptionTask" << std::endl;

    // Start throwExceptionTask asynchronously (now or later or never)
    auto f1 = std::async(throwExceptionTask);  

    std::cin.get();

    std::cout << "\nWait for the end of throwExceptionTask" << std::endl;

    try {
        // Wait for throwExceptionTask() to finish (raises exception if any)
        f1.get();  
    } catch (const std::exception& e) {
        std::cout << "EXCEPTION: " << e.what() << std::endl;
    }
}

void waitingForTwoTasks()
{
    std::cout << "Starting 2 operations asynchronously:" << std::endl;

    auto f1 = std::async([]{ randomSleepAndOutput('.'); });
    auto f2 = std::async([]{ randomSleepAndOutput('+'); });
    auto isOneOfThemRunning = [&f1, &f2]() {
        return f1.wait_for(std::chrono::seconds(0)) != std::future_status::deferred ||
               f2.wait_for(std::chrono::seconds(0)) != std::future_status::deferred;
    };
    auto isOneOfThemReady = [&f1, &f2]() {
        return f1.wait_for(std::chrono::seconds(0)) == std::future_status::ready ||
               f2.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
    };

    if (isOneOfThemRunning()) {
        while (!isOneOfThemReady()) {
        //...;
        std::this_thread::yield();  // hint to reschedule to the next thread
        }
    }
    std::cout.put('\n').flush();

    try {
        f1.get();
        f2.get();
    } catch (const std::exception& e) {
        std::cout << "\nEXCEPTION: " << e.what() << std::endl;
    }
    std::cout << "\ndone" << std::endl;
}

int main()
{
    parallelExecution();
    lazyEvaluation();
    parallelExecutionWithException();
    waitingForTwoTasks();
}


std::future<int> result1(std::async(lambda1));
  • async(): 提供一個介面,讓程式在允許情況下,使用一個獨立執行緒在背景執行 callable object
    • async 會試著啟動 thread 來執行 callable object
    • 不一定總是可在背景用獨立執行緒執行,可能不會被執行,例如單執行緒環境下
    • callable object 後的參數可以放欲傳入的引數,例如:std::async(someLambda, argx, argy);  or std::async(&X::mem, x , 42); // try to call x.mem(42)
  • future(): 提供介面讓使用者等待 thread 完成執行,並且可以存取執行結果(回傳值或例外)
    • 藉由 callable object 的 return type 來 Specialize
    • 若 callable object 僅背景執行任務,則為 std::future<void>
    • 透過 get() or wait(),可強迫 callable object 被執行
    • future 與 async callable object 交換 data 的機制,是透過共同存取一個 "shared state"


int result = result1.get() + result2;
  • get(): 
    • 呼叫 get 時,以下三種可能情況會發生:
      • callable 已經透過 async() 在另一個 thread 啟動,且已經完成執行,會立即得到結果
      • callable 已經透過 async() 在另一個 thread 啟動,但尚未執行完畢,get() 會 block 直到執行結束並產生結果
      • callable 尚未開始執行,呼叫 get 會強制 callable 開始執行(如同 synchronous 呼叫),get 會 block 直到執行結束並產生結果
    • 以上行為直接保證在單執行緒的環境下,也可得到正確的執行結果
    • 若沒有呼叫 get(),async 亦沒有執行 callable,則 main() 結束時此函式會完全沒有被執行
    • get 只可以被呼叫一次。呼叫後,future 進入 invalid 狀態
  • 平行化程式的撰寫方法(使其亦相容於單執行緒環境):
    1. include <future>
    2. 可平行化執行的 callable 傳給 async
    3. async 結果指派給 future<ReturnType>
    4. 當想需要 callable 結果或是想要確保被啟動的 callable 在此處完成時,呼叫 future 的 get()

std::future<int> result1(std::async(lambda1));
int result = lambda2() + result1.get();
  • 上述寫法,看似可以透過 async 啟動 lambda1,再透過第二行 synchronous 執行 lambda2,但其實有可能完全得不到平行化的好處。因為右側 evaluate 的順序並沒有規定,有可能先執行 get() 進而 synchronous 執行 lambda1,再執行 lambda2
  • Call early and return late
    • 即最大化呼叫 async 與 get 之間的距離
    • 可得到平行化最好的效果

std::future<int> result1(std::async(std::launch::async, lambda1));
  • Launch policy
    • std::launch::async
      • 強迫立即 asynchronously 啟動或丟出 std::system_error 的例外
      • 不需要再呼叫 get(),因為若 future 的 lifetime 結束時,程式會等待 lambda1 執行完成
      • std::async(std::launch::async, ...) 沒有指派給 future,則變成 synchronous 呼叫,會 block 直到 lambda1 執行結束
    • std::launch::deferred
      • defer lambda1 直到 get 被呼叫
      • 可用來實作 lazy evaluation
    • 是 scoped enumeration,所以需要 std::launch or launch



// Try to call lambda1 asynchronously
std::future<int> result(std::async(lambda1));

...;

// Wait for lambda1 to be done(might start it)
result.wait();
  • wait()
    • 相較於 get 只能執行一次,wait 提供一個介面可以等待 callable 執行完成,同時不會去存取執行結果
    • 效果: 強迫 future 對應的 thread 啟動,且等待至其執行結束
    • wait 系列介面,可被多次呼叫
  • wait_for(), wait_until()
    • 若 callable 尚未啟動,不會強迫 future 對應的 thread 啟動
    • wait_for 可帶入 duration
    • wait_until 可帶入 timepoint
    • 兩者皆會回傳以下其一:
      • std::future_status::deferred: async 延遲執行,且沒有 wait 或 get 被呼叫而強迫啟動
      • std::future_status::timeout: 已啟動執行,但尚未執行完畢,且等待的人超過 timeout
      • std::future_status::ready: 執行完成
    • 可達到 Speculative Execution [推測執行]
      • 是一種最佳化技術,可利用空轉的時間去做其他事
    • 可以用來 "poll" 判斷背景任務是否已啟動或執行中:
      • f.wait_for(chrono::seconds(0)) != future_status::ready


share_future<int> f = async(CallableObj);
  • std::future 只可以呼叫一次 get,呼叫第二次會產生 undefined behavior(C++標準實作鼓勵丟出 std::future_error)
  • 需要多次存取結果值的情況,可用 std::shared_future
  • share_future<int> f = async(CallableObj).share();
  • 比較介面:
    • T future<T>::get();
    • const T& shared_future<T>::get()



Reference:

  1. The C++ Standard Library: A Tutorial and Reference




2018年10月22日 星期一

[Effective C++] Make sure that objects are initialized before they're used

/* 確保 object 在被使用前已初始化 */

// 1. Reading uninitialized values yields undefined behavior
// 2. Best: always initialize your objects before you use them

// Non-member objects of built-in types
int x = 0;
const char * text = "A C-style string";

double d;
std::cin >> d;

// For almost everything else: make sure that all ctor initialize everything in the object
class PhoneNumber {};
class ABEntry {
public:
 ABEntry();
 ABEntry(const string& name, const string& address, const std::list<PhoneNumber> &phones);

private:
 std::string theName;
 std::string theAddress;
 std::list<PhoneNumber> thePhones;
 int numTimesConsulted;
};
ABEntry::ABEntry(const string& name, const string& address, const std::list<PhoneNumber>& phones)
{
 theName = name;
 theAddress = address;
 thePhones = phones;
 numTimesConsulted = 0;
 // these are all assignments not initializations
}

// 1. The data members of an object are initialized before the body of a constructor is entered.
// 2. Use the member initialization list instead of assignments: more efficient
ABEntry::ABEntry(const string& name, const string& address, const std::list<PhoneNumber>& phones)
 :theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) // these are now all initializations
{}

// Default-construct a data member
ABEntry::ABEntry()
 : theName(), theAddress(), thePhones(), numTimesConsulted(0)
{}

// For data member that are const or are references must be initialized by initialization list

// 1. Duplication when multiple ctors: by a single private function that all the ctors call.
// 2. In general, true member initialization(via an initialization list) is preferable to pseudo-initialization via assignment

// Initialization order: base class before derived class, data member is initialized in the order where they are declared.

// 1. Static object: one that exists from the time it's constructed until the end of the program, automatically 
// destroyed when the program exits, i.e., dtors automatically called when main finishes executing.
// 2. local static object: static objects inside functions; non-local static object: others
// 3. A translation unit: source code giving rise to a single object file (a single source file + all of its #include files)
// 4. 若一個 translation unit 的 non-local static object 之初始化用到另一個 translation unit 中的 non-local static object, 則
// 被使用的 object 可能未被初始化 -> 因為定義在不同 translation unit 的 non-local static objects 之初始化相對順序是未定義的
class FileSystem {
public:
 std::size_t numDisks() const;
};
extern FileSystem tfs;

class Directory {
public:
 Directory(void * pParams);
};
Directory::Directory(void * pParams)
{
 std::size_t disks = tfs.numDisks();
}
Directory tempDir(nullptr);// static object: global variable
// How can we be sure tfs will be initialized before tempDir? No Way.

// Solution: Singleton Pattern
FileSystem& tfs() // could be a static member function in the FileSystem class
{
 static FileSystem fs;
 return fs;
}
Directory::Directory(void * pParams)
{
 std::size_t disks = tfs().numDisks();
}
Directory& tempDir() // could be a static member function in the Directory class
{
 static Directory td;
 return td;
}
// 1. If never call, never incur the cost of constructing and destructing the object
// 2. Excellent candidates for inlining

// 1. Function contain static objects makes them problematic in multi-threaded systems.
// 2. Solution: manually invoke all the reference-returning functions during the single-threaded startup portion of the program.