2018年11月27日 星期二

[Linux Shell Programming] 批次改檔名

常常遇到需要批次把目錄下的所有檔案名稱的某個前綴字或後綴字去除後,以此重新命名檔案,可參考以下流程:
  1. 列出目錄下所有檔案並用 grep 找出目標檔案
  2. 使用 for loop 爬過每個檔案名稱並使用 sed 得到另外一個名稱
程式碼:


1
2
3
4
5
6
7
8
files=$(ls | grep '@2x.png$')

for f in $files
do
    echo $f
    g=$(echo $f | sed 's/@2x//g')
    echo $g
done

以上是核心概念,接下來再小修改程式滿足特定需求就可以了


2018年11月18日 星期日

C++ 學習筆記:chrono - Timepoint 與 C Calendar Time (ctime) 轉換

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
#include <chrono>
#include <ctime>
#include <string>
#include <iostream>

std::string asString(const std::chrono::system_clock::time_point& tp)
{
    constexpr bool blUseLocalTime = true;
    std::time_t t = std::chrono::system_clock::to_time_t(tp);
    std::string ts = blUseLocalTime ? std::ctime(&t) : std::asctime(gmtime(&t));
    ts.resize(ts.size() - 1);

    return ts; 
}

std::chrono::system_clock::time_point makeTimePoint (int year, int mon, int day,
                                                     int hour, int min, int sec = 0)
{
    struct std::tm t;
    t.tm_sec = sec;        // second of minute (0 .. 59 and 60 for leap seconds)
    t.tm_min = min;        // minute of hour (0 .. 59)
    t.tm_hour = hour;      // hour of day (0 .. 23)
    t.tm_mday = day;       // day of month (1 .. 31)
    t.tm_mon = mon - 1;    // month of year (0 .. 11)
    t.tm_year = year - 1900; // year since 1900
    t.tm_isdst = -1;       // determine whether daylight saving time

    auto tt = std::mktime(&t);
    if (tt == -1) {
        throw "no valid system time";
    }

    return std::chrono::system_clock::from_time_t(tt);
}

int main()
{
    auto tp1 = makeTimePoint(2010, 01, 01, 00, 00);
    std::cout << asString(tp1) << std::endl;

    auto tp2 = makeTimePoint(2011, 05, 23, 13, 44);
    std::cout << asString(tp2) << std::endl;
}

Note:
mktime 會考慮 local time zone, 沒有明確的方法可以指定 UTC time

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

C++ 學習筆記:chrono - Part 2 clock 與 timepoint

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
#include <chrono>
#include <iostream>
#include <iomanip>
#include <thread>
#include <ctime>
#include <string>
#include <vector>
#include <numeric>

template <typename T>
void printClockData()
{
    using PeriodType = typename T::period;

    std::cout << " - precision: ";
    std::cout << static_cast<double>(PeriodType::num) / PeriodType::den << " seconds" << std::endl;
    std::cout << " - is_steady: " << std::boolalpha << T::is_steady << std::endl;
}

std::string asString(const std::chrono::system_clock::time_point& tp)
{
    constexpr bool blUseLocalTime = false;
    std::time_t t = std::chrono::system_clock::to_time_t(tp);
    std::string ts = blUseLocalTime ? std::ctime(&t) : std::asctime(gmtime(&t));
    ts.resize(ts.size() - 1);

    return ts; 
}

volatile int sink;
int main()
{
    /* Clock */
    std::cout << "system_clock: " << std::endl;
    printClockData<std::chrono::system_clock>();
    std::cout << "\nsteady_clock: " << std::endl;
    printClockData<std::chrono::steady_clock>();
    std::cout << "\nhigh_resolution_clock: " << std::endl;
    printClockData<std::chrono::high_resolution_clock>();

    /* system_clock */
    auto system_start = std::chrono::system_clock::now();
    // do some heavy work here, more than five seconds
    using namespace std::chrono_literals;
    // std::this_thread::sleep_for(6s);
    if (std::chrono::system_clock::now() > system_start + std::chrono::seconds(5)) {
        // If someone adjust the clock, condition may produce "false"
        std::cout << "Checked!" << std::endl;
    }

    auto diffSec = std::chrono::system_clock::now() - system_start;
    auto sec = std::chrono::duration_cast<std::chrono::milliseconds>(diffSec);
    std::cout << "This program runs: " << sec.count() << " milliseconds" << std::endl;

    /* steady_clock */
    for (auto size = 1ull; size < 1000000000ull; size *= 100) {
        auto start = std::chrono::steady_clock::now();
        
        std::vector<int> Vec(size, 123);
        sink = std::accumulate(Vec.begin(), Vec.end(), 0u); // make sure it's a side effect
        
        auto end = std::chrono::steady_clock::now();
        std::chrono::duration<double> diffSec = end - start;
        std::cout << "Time to fill and iterate a vector of " << size << " ints : " << diffSec.count() << " s\n";
    }

    /* Time Point */
    std::chrono::system_clock::time_point tp;
    auto sysClockNow = std::chrono::system_clock::now();
    auto sysClockMin = std::chrono::system_clock::time_point::min();
    auto sysClockMax = std::chrono::system_clock::time_point::max();
    std::cout << "epoch:          " << asString(tp) << std::endl;
    std::cout << "current time:   " << asString(sysClockNow) << std::endl;
    std::cout << "minimum time:   " << asString(sysClockMin) << std::endl;
    std::cout << "maximum time:   " << asString(sysClockMax) << std::endl;

    /* Timepoint and Duration Arithmetic */
    using TypeDays = std::chrono::duration<int, std::ratio<3600 * 24>>;

    std::chrono::time_point<std::chrono::system_clock> timePoint;
    std::cout << "epoch:     " << asString(tp) << std::endl;

    tp += TypeDays(1) + std::chrono::hours(23) + std::chrono::minutes(55);
    auto diff = tp - std::chrono::system_clock::time_point();
    auto minutesDiff = std::chrono::duration_cast<std::chrono::minutes>(diff);
    TypeDays daysDiff = std::chrono::duration_cast<TypeDays>(diff);
    std::cout << "later:     " << asString(tp) << std::endl;
    std::cout << "diff:      " << minutesDiff.count() << " minute(s)" << std::endl;
    std::cout << "diff:      " << daysDiff.count() << " day(s)" << std::endl;

    tp -= std::chrono::hours(24 * 365);
    std::cout << "-1 year:   " << asString(tp) << std::endl;

    tp -= std::chrono::duration<int, std::ratio<3600 * 24 * 365>>(50);
    std::cout << "-50 years: " << asString(tp) << std::endl;

    tp -= std::chrono::duration<int, std::ratio<3600 * 24 * 365>>(505000);
    std::cout << "-505000 years: " << asString(tp) << std::endl;// underflow
}


chrono 的 clock 與 time point

  • clock
    • 用來定義一個 epoch (time_point) 與一個 tick period/rate (duration),不同的 clock 有不同的 epoch例如:
      • epoch: std::chrono::system_clock::time_point
      • tick period: std::chrono::system_clock::period
    • C++ 提供三種 clock:
      • system_clock
        • 系統上真實時間的 clock (system-wide real time wall clock)
        • 提供 function 可以與 C system time time_t 做轉換,且僅有 system_clock 支援此功能
        • 可能不是單調遞增的,因為系統時間可以隨時被調整
      • steady_clock
        • 單調遞增的 clock (monotonic clock)
        • 相較於 system_clock,絕對不會被調整,即隨著真實時間演進,此 clock 的 timepoint 絕對不會變小
        • 不會對應到真實時間,可以是上次 reboot 時間
        • 非常適合用來測量時間間隔
      • high_resolution_clock
        • 系統上具有最短 tick period 的 clock
        • 它可能是 system_clock 或 steady_clock 或是另外一個獨立的 clock 的別名
    • 關於 system_clock
      • std::chrono::system_clock::now() 會根據系統上的時鐘時間調整,所以有一些運算或是條件判斷是不可信的
      • 相較於 system_clock,steady_clock 時間是單調遞增的,運算或是條件式可信
    • 根據使用的 clock,提供一個 time_point 型別給 timepoint
    • 使用 clock 的 static member function now 可以取得當前的 time_point
  • timepoint
    • 給定一個 clock 下,根據一個正或負的 duration 來表示一個特定的時間點
      • 透過提取 clock 的 time_point (通常是 epoch 或是 now()),再加減上一個 duration 得到另外一個 time_point
    • time point 可以表示為(T 為上述三種 clock 任一種)
      • std::chrono::T::time_point
      • std::chrono::time_point<T>
    • 一個 clock 的 time_point 型別具有以下三個重點
      • epoch: default-construct 的 time_point 即是 epoch (講白話,起始時間)
      • min: 可產生最小的 time_point
      • max: 可產生最大的 time_point
    • timepoint 在做算術運算時,不考慮閏年


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

C++ 學習筆記:chrono - Part 1 duration

Hello World 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
#include <chrono>
#include <ratio>
#include <iostream>
#include <iomanip>

template <typename T>
class TD;

template <typename _Value, typename _Fraction>
std::ostream& operator << (std::ostream& os, const std::chrono::duration<_Value, _Fraction>& dur)
{
    os << "[" << dur.count() << " of " << _Fraction::num << "/" << _Fraction::den << " sec]";
    return os;
}

int main()
{
    /* Basic Representation */
    std::chrono::duration<int> twentySec(20);  // value: 20, fraction: 1 sec (default)
    std::chrono::duration<double, std::ratio<60>> halfMin(0.5); // value: 0.5, fraction: 60 sec
    std::chrono::duration<long, std::milli> oneMs(1);  // value: 1, fraction: 1 milli sec

    /* Use Helper Class */
    std::chrono::seconds twentySec2(20);
    std::chrono::hours oneDay(24);
    std::chrono::microseconds oneMs2(1);
    //std::chrono::minutes halfMin2(0.5);// compile error, the duration type is int

    /* Arithmetic */
    std::chrono::seconds d1(42);
    std::chrono::milliseconds d2(10);
    auto diff = d1 - d2;
    std::cout << diff.count() << std::endl; // 41990 (of 1/1000 sec)

    std::chrono::duration<int, std::ratio<1, 3>> d3(1);
    std::chrono::duration<int, std::ratio<1, 5>> d4(1);
    auto add = d3 + d4;
    std::cout << add.count() << std::endl; // 8 (of 1/15 sec)
    std::cout << std::boolalpha << (d3 < d4) << std::endl;// false

    /* Arithmetic Return Type */
    //TD<decltype(diff)> x;// compiler deduce: TD<std::chrono::duration<__int64,std::milli>>
    //TD<decltype(add)> x;// compiler deduce: TD<std::chrono::duration<int,std::ratio<0x01,0x0f>>>
    //TD<decltype(twentySec2 - oneDay)> x;// compiler deduce: TD<std::chrono::duration<__int64,std::ratio<0x01,0x01>>>
    //TD<decltype(twentySec2 - oneMs2)> x;// compiler deduce: TD<std::chrono::duration<__int64,std::micro>>

    /* Arithmetic and Conversion */
    std::chrono::milliseconds ms(0);    // undefined value without (0)
    ms += twentySec2 + oneDay;
    --ms;
    ms *= 2;
    std::cout << ms.count() << " ms" << std::endl;//172839998 ms

    std::chrono::nanoseconds ns(ms);
    std::cout << ns.count() << " ns" << std::endl;// 172839998000000 ns

    /* Static Operations */
    auto zeroMS = std::chrono::milliseconds::zero();// duration with zero value
    auto maxMS = std::chrono::milliseconds::max();// duration with max value
    auto minMS = std::chrono::milliseconds::min();// duration with min value

    using TypeMsValue = std::chrono::milliseconds::rep;
    using TypeMsFraction = std::chrono::milliseconds::period;

    std::cout << twentySec << std::endl;    // use overloaded operator <<
    std::cout << d3 << std::endl;           // use overloaded operator <<

    /* Explicit Conversion */
    // example 1: convert to different fraction
    std::chrono::seconds sec(55);
    //std::chrono::minutes m1 = sec;  // compile-time error
    std::chrono::minutes m1 = std::chrono::duration_cast<std::chrono::minutes>(sec);
    std::cout << m1 << std::endl;   // value 0 due to information lost

    // example 2: conver to different value type, double to long long
    halfMin;// type: std::chrono::duration<double, std::ratio<60>>
    //std::chrono::seconds s1 = halfMin; // compile-time error
    std::chrono::seconds s1 = std::chrono::duration_cast<std::chrono::seconds>(halfMin);
    std::cout << s1 << std::endl;   // [30 of 1/1 sec]

    // example 3: segment into different units
    ms = std::chrono::milliseconds(7255042);
    std::chrono::hours hh = std::chrono::duration_cast<std::chrono::hours>(ms);
    std::chrono::minutes mm = std::chrono::duration_cast<std::chrono::minutes>(ms % std::chrono::hours(1));
    std::chrono::seconds ss = std::chrono::duration_cast<std::chrono::seconds>(ms % std::chrono::minutes(1));
    std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds>(ms % std::chrono::seconds(1));

    std::cout << "raw: " << hh << "::" << mm <<  "::" << ss << "::" << msec << std::endl;
    std::cout << "     " << std::setfill('0') << std::setw(2) << hh.count() << "::"
                                              << std::setw(2) << mm.count() << "::"
                                              << std::setw(2) << ss.count() << "::"
                                              << std::setw(2) << msec.count() << std::endl;
}


chrono

chrono library 是一個可以提供時間與日期且精準度中性的 C++11 函式庫
主要成分:
  1. duration: 在一個時間單位下的數量。例如,3 個 分鐘(minute)  表示 3 分鐘、3 個 0.5秒(0.5 seconds) 表示 1.5 秒
  2. timepoint: 一個起始時間(epoch) 與一個 duration 的組合。例如,千禧年午夜,可表示成 自從 1970 年 1 月 1 日起 1262300400 秒
  3. clock: 用來定義 epoch,不同的 clock 有不同的 epoch

Duration

  • 為 value 與 fraction 的組合,其中:
    • value
      • 表示次數
      • duration 的第一個 template argument 為此次數的型別
    • fraction
      • 表示以秒為基準的單位
      • 常用 ratio 來表示
  • 常用的 helper type 可參考此 Link
    • 注意:value 的 type 僅支援 signed integer
  • 若使用 default ctor 建構 duration,(即不傳入任何參數),其值是 undefined


Duration 的運算

  • 兩個 duration 運算後,以秒為基準的單位可能會改變,即 fraction 部分可能會不同
    • 回傳型別是 common_type 的 SPECIALIZATIONS,為一個 duraiton,其中:
      • value 定義為兩個 duration value 的 common_type
      • fraction 定義為 ratio<兩者最大公因數, 兩者最小公倍數>
  • 不同 Duration 間轉換
    • implicit type conversion
      • 一個 duration 也可透過 ctor 或 assignment 轉成另一個單位的 duration,只要有支援 implicit type conversion (僅限於大單位轉小單位,即變得更精準的情況)
    • duration_cast
      • 若要從小單位轉大單位,即有機會造成資訊流失情況下,需要用 duration_cast,例如 42010 ms 轉成 42 s
Note:
使用 Visual Studio 2013 測試上面的 code,發現 84 到 86 行不 work,應該是 VS 的 lib 實作有問題,回傳型別方面的問題,這邊就不深入研究此錯誤了。另一方面,使用 g++ 就完全正常。

Reference:

  1. The C++ Standard Library: A Tutorial and Reference, Second Edition
  2. Visual Studio 2013

C++ 學習筆記:ratio

Compile-time 的分數與算術運算

Hello World 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
#include <iostream>
#include <ratio>
#include <cstdint>

int main()
{
    /* Representation */
    using FiveThirds = std::ratio < 5, 3 > ;
    std::cout << FiveThirds::num << "/" << FiveThirds::den << std::endl;            // 5/3

    using AlsoFiveThirds = std::ratio < 25, 15 >;
    std::cout << AlsoFiveThirds::num << "/" << AlsoFiveThirds::den << std::endl;    // 5/3

    std::ratio<42, 42> one;
    std::cout << one.num << "/" << one.den << std::endl;                            // 1/1

    using Zero = std::ratio<0>;  // denominator default 1
    Zero zero;
    std::cout << zero.num << "/" << zero.den << std::endl;                          // 0/1

    using Neg = std::ratio < 7, -3 >;
    std::cout << Neg::num << "/" << Neg::den << std::endl;                          // -7/3

    /* Arithmetics: meta functions */
    using Addtype = std::ratio_add<FiveThirds, AlsoFiveThirds>::type;
    std::cout << Addtype::num << "/" << Addtype::den << std::endl;                  // 10/3

    std::cout << std::boolalpha
              << std::ratio_equal<FiveThirds, AlsoFiveThirds>::value << std::endl;  // true

    //std::ratio_multiply < std::ratio<1, INTMAX_MAX>, std::ratio<1, 2> >;          // compile error
    //std::ratio_divide<FiveThirds, Zero>;                                          // compile error
    //std::ratio_divide<FiveThirds, Zero>::type;                                    // compile error

    /* Predefined units */
    using NanoType = std::ratio<1, 1000000000LL>::type;
    std::cout << std::boolalpha << std::ratio_less<std::nano, NanoType>::value << std::endl;// false
    std::cout << std::boolalpha << std::ratio_less<NanoType, std::nano>::value << std::endl;// false
    std::cout << std::boolalpha << std::ratio_equal<std::nano, NanoType>::value << std::endl;// true
}


class 大致定義如下:



#include <cstdint>
namespace std
{
    template <intmax_t N, intmax_t D = 1>
    struct ratio {
        static_assert(D != 0, "denominator cannot be zero");
        static_assert(N >= -INTMAX_MAX && D >= -INTMAX_MAX, "out of range");

        static constexpr intmax_t num = ...;
        static constexpr intmax_t den = ...;
        
        using type = ratio < num, dem > ;
    };
}


  • 主要用途為在 compile time 表示一組分數
  • 在編譯期可自動抓取 divide-by-zero 與 overflow 的錯誤
  • 常被使用在 chrono 上

使用方法:

  1. 給定分子與分母,定義一個 type(可使用 using)
  2. 使用 static constexpr 的 num 與 den
  3. Predefine 的 unit 可參考此 Link

Reference:

  1. The C++ Standard Library: A Tutorial and Reference, Second Edition
  2. https://gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/a01002_source.html
  3. Visual Studio 2013 Library

2018年11月8日 星期四

[C++ Meta-programming] 實作將 reference 型別加上 const 的 metafunction


 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
#include <type_traits>
#include <iostream>

template <typename T>
struct add_const_ref
{
    typedef const T& type;
};

template <typename T>
struct add_const_ref<T&>
{
    typedef T& type;
};

template <typename T>
struct add_const_ref2 : std::conditional<std::is_reference<T>::value,
                                        T,
                                        const T&>
{

};

int main()
{
    std::cout << std::is_same<add_const_ref<int&>::type, int&>::value << std::endl;
    std::cout << std::is_same<add_const_ref<int>::type, const int&>::value << std::endl;

    std::cout << std::is_same<add_const_ref2<int&>::type, int&>::value << std::endl;
    std::cout << std::is_same<add_const_ref2<int>::type, const int&>::value << std::endl;
}

思考脈絡

  • Primary template
    • 一般情況,定義一個新型別在 add_const_ref 內
    • 根據傳入型別 T 直接加上 const 和 &
  • Partial specialization
    • 遇到傳入型別是 reference 就走這個 case
    • 根據傳入型別 T,直接加上&,定義一個 T&


[C++ Meta-programming] 實作 Pointer 判斷器


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

/* Primary template */
template <typename T>
struct IsPointer
{
    static const bool value = false;
};

/* Partial Specialization */
template <typename T>
struct IsPointer<T*>
{
    static const bool value = true;
};

int main()
{
    std::cout << "IsPointer<int *>::value = " << IsPointer<int *>::value << std::endl;
    std::cout << "IsPointer<int>::value = " << IsPointer<int>::value << std::endl;
}

思考脈絡

  • Primary template 先設計好
    • static member 值 false
    • 一般情況,帶入的 template argument 會走這
  • Partial Specialization
    • static member 值帶 true
    • 若傳入的 template argument 是 pointer 的類型,compiler 會用 instatiate 這個template,得到 static member value 是 true 的 struct
  • 使用 IsPointer<型別>::value 得到對應的結果


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;
}