避免function argument implicit conversion的方式

在std::string中,如果我們想要assign char
例如

std::string s = ‘A’; 這樣是不行的

必須要 std::string s(1, ‘A’);

std::string的constructor 參考 http://www.cplusplus.com/reference/string/string/string/

string(); //default (1)	
string (const string& str); //copy (2)	
string (const string& str, size_t pos, size_t len = npos); //substring (3)	
string (const char* s); //from c-string (4)
string (const char* s, size_t n); // from buffer (5)	
string (size_t n, char c); //fill (6)	
template <class InputIterator> string  (InputIterator first, InputIterator last); //range (7)	
string (initializer_list<char> il); //initializer list (8)	
string (string&& str) noexcept; //move (9)

假想我們是string的設計者,加一個 string(char c); 會如何呢?

這樣看起來似乎完美方便,但其實在使用上多出了一些意想不到的情況

例如 std::string s = 123.4;

這樣就會走 string(char c);這條路而可以compile過,理由是Floating-integral conversions

An rvalue of a floating point type can be converted to an rvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.

參考 C++03 4.9 Floating-integral conversions

另外還包括 std::string s = 123;

Integral conversion(非integral promotion)說明在integral type之間可以implicit互轉
An rvalue of an integer type can be converted to an rvalue of another integer type. An rvalue of an enumeration
type can be converted to an rvalue of an integer type.

參考: C++03 4.7 Integral conversions

因此開了一個char的路,導致floating point, integral type都可以初始化了
事實上 std::string s(1, ‘A’); 如果改成 std::string s(1, (long)65);也是可以compile過

在API的設計 我們如果想要避免insidious bug,若能在compile time對型態檢查發現就可以減少使用上潛在的問題。

以下參考 https://stackoverflow.com/questions/12877546/how-do-i-avoid-implicit-conversions-on-non-constructing-functions 整理C++11兩種做法

第一種作法是blacklist

利用deleted function讓compiler挑到該function而失敗。例如:
string(int v) = delete;
這種作法需要針對可能implicit conversion的case都要處理。以char的例子來說,我們需要排除掉各種可能的轉換,並且一旦定義了兩個可接受integral type的constructor — 以上面例子 char & int ,就要小心如果傳入的型態沒有exact match就會走promotion or conversion的路,這時候很可能會ambiguous。(當然,ambiguous也是compile不過,雖然不是我們希望走到的規則)

假如我們定義

string(char c);
string(int v) = delete;

string s = 2.3; //ambigous, floating point to integral conversion, choose char or int
string s = (short)2; //integral promotion, call string(int) delete function
string s = (long) 2; //ambigous, integral conversion, choose char or int

因此這種作法最好是要定義全面
將 int, unsigned int, long, unsigned long, long long, unsigned long long, double都 delete

不須對bool, unsigned char, short, unsigned short定義的原因是因為沒有exact match時會走integral promotion到int (但注意標準是說如果int可以容納得下上述的type的話,否則就會轉成unsigned int,這種情況在LP32-short, int都是2 bytes時會發生)

第二種做法是whitelist

直接用template把所有其他的type都delete

template <typename T>
string(T) = delete;

string s = ‘A’;會產生兩個選擇viable function。一個是string(char c); 另一個是template <> string(char) ,但是根據 overload resolution的規則

a viable function F1 is defined to be a better function than another viable function
F2 if…and then…or, if not that,
F1 is a non-template function and F2 is a function template specialization

C++03 13.3.3 Best Viable Function

因此,透過定義template deleted function的效果等於將沒有顯式定義的type的constructor都delete掉。

C++03沒有deleted function的機制,可以用以下方式讓compile fail

class DeleteOverload
{
private:
    DeleteOverload(void*);
};
template<typename T>
string(T, DeleteOverload = 0);

example:

#include <iostream>
#include <string>
/*
//C++03
class DeleteOverload
{
private:
    DeleteOverload(void*);
};
*/
class X
{
public:
  X(char c)
  {
  }
  template<typename T>
  X(T) = delete; //C++11
  /*
  //C++03
  template<typename T>
  X(T, DeleteOverload = 0);
  */
};
int main()
{
  X x = 1.23;
  return 0;
}
Posted in C++ Language | Leave a comment

bool conversion test

後記:以下主要是展示template處理explicit bool operator的檢查所用的技巧,但其實真正要做cast檢查這樣檢查太瑣碎也不全面,可以透過std::is_constructiblestd::is_convertible來處理,留待之後的文章整理

在C++中如果我們要知道物件轉成bool的值,可以直接用cast operator將型別轉成bool
例如 (bool)10.0 => true

但是像是

class X 
{
};
X x;
(bool)x;

這段代碼顯然是無法通過編譯的(invalid cast from type ‘X’ to type ‘bool’),
因為X沒有定義bool轉型的函式

假如我們想要設計一個function GetBool 可以檢查物件是否可轉成bool
─ 如果可以轉型就返回轉型的bool value, 如果不能轉型就返回false

因為使用的型別事先未知,所以勢必要使用template,根據使用者呼叫的參數來決定回傳值。因此很直覺的可以想到應該可以這樣做:

template<typename T>
bool GetBool(T t)
{
  return (bool)t;
}

但是這樣做有個問題 ─ 那就是前面提到的,有些類別型別不支援bool轉型(沒有實作bool operator) ,必須在compile time要分兩個function,一個是可以轉型的,一個是不能轉型的,可是要讓呼叫者自己分辨傳入的參數顯然不太方便,這時候可以透過template metaprogramming的一些技巧讓compiler在compile time自動推導處理

這邊展示的GetBool作法參考了 gcc libstdc++-v3的is_convertible的概念實作
─ 透過helper class推導”type” member field的型別
std::true_type /std::false_type
─ 再利用繼承該std::integral_constant型別讓後續template處理取得取得對應的bool value

#include <type_traits>
#include <iostream>
#include <functional>
#include <memory>

template <typename T>
class has_bool_operator_helper
{
private:
    template <typename C, typename = decltype(&C::operator bool)> static std::true_type test(int) ;
    template <typename> static std::false_type test(...);    

public:
    typedef decltype(test<T>(0)) type;
};

template <typename T>
class has_bool_operator: public has_bool_operator_helper<T>::type
{
};
透過decltype(test<T>(0))在compile決定"type" member field的型別
test<T>(0)會讓compiler選擇我們定義的兩個function
至於哪一個function會被implicit convertion和overload resolution來決定
例如test<T>(0) -> 他會優先選擇test(int)

因為0的型別本身就是int,事實上如果比對的函式如果是test(bool)也會優先於test(...)比對,原因是standard conversion sequencec會優先於ellopsis conversion sequence
參考 C++03 13.3.3.2 Ranking implicit conversion sequences

has_bool_operator_helper這邊使用SFINAE的技巧, 可參考https://en.cppreference.com/w/cpp/language/sfinae

overload resolution過程選擇後如果發生subsitution fail,compiler不會報錯,而是繼續找下一個符合的function。

因此如果decltype(&C::operator bool)>失敗就會採用static std::false_type test(…)的function

但是operator bool是用於class,如果對於一些fundamental types或是pointer等就不適用。

因此需要利用std type_traits提供的is_convertible檢查是否可轉型,注意is_convertible他是透過implicit conversion的方式利用類似上面提到的方法來測試 因此不能implicit conversion到bool的型別is_convertible會回報false

GetBool就利用std::is_convertible和has_bool_operator來判斷型別是否支援bool轉型 在下面分成3類
─ is_convertible
─ !is_convertible && has_bool_operator
─ !is_convertible && !has_bool_operator
分成這3類主要是為了更清晰的展示template判斷的規則
事實上其實只要分成
─ is_convertible || has_bool_operator
─ !is_convertible && !has_bool_operator
這兩類就可以

template <class T>
typename std::enable_if<std::is_convertible<T, bool>::value, bool>::type
GetBool(T t)
{
  std::cout << "choose convertible" << std::endl;
  return (bool)t;
}

template <class T>
typename std::enable_if<!std::is_convertible<T, bool>::value && !has_bool_operator<T>::value, bool>::type
GetBool(T t)
{
  std::cout << "choose false" << std::endl;
  return false;
}

template <class T>
typename std::enable_if<!std::is_convertible<T, bool>::value && has_bool_operator<T>::value, bool>::type
GetBool(T t)
{
  std::cout << "choose bool operator" << std::endl;
  return (bool)t;
}
template<typename T>
void test(T x)
{
  std::cout << GetBool(x) << std::endl;
}
class X1
{
};

class X2
{
public:
  operator bool() const
  {
    return true;
  }
};
class X3
{
public:
  explicit operator bool() const
  {
    return true;
  }
};
class X4
{
  //private
  operator bool() const
  {
    return true;
  }
};
X1 x1;
X2 x2;
X3 x3;
X4 x4;
void f1(){}
std::function<void()> f2;
std::function<void()> f3 = f1;
std::shared_ptr<int> sp1;
std::shared_ptr<int> sp2(new int);
enum en{E1, E2};
en e1 = E1;
en e2 = E2;
int* p1 = NULL;
int p2 = 0;
int* p3 = &p2;
int arr[5];
std::string s1;
int main()
{ 
  test(f1); //convertible => 1
  test(f2); //bool operator => 0, std::function的bool operator, f2沒有assign, 返回false
  test(f3); //bool operator =>  1, std::function的bool operator
  test(x1); //choose false, X1沒有定義bool conversion
  test(x2); //convertible => 1, X2定義bool conversion (因為可以implicit conversion 所以走is_convertible那條路)
  test(x3); //bool operator => 1, X3定義explicit bool conversion(因為無法implicit conversion 所以走bool operator check那條路)
  test(x4); //choose false, X4雖然定義bool conversion但是private
  test(true); //convertible=>1
  test(false); //convertible=>0
  test(100.0); //convertible=>1
  test(0.0); //convertible=>0
  test(-1); //convertible=>1
  test(p1); //convertible=>0, null pointer
  test(p2); //convertible=>0
  test(p3); //convertible=>1, not null pointer
  test(nullptr); //choose false, nullptr無法implicit轉型, 也無bool operator
  test(sp1); //bool operator => 0, pointer not set
  test(sp2); //bool operator => 1
  test(e1); //convertible=>0, enum value 0
  test(e2); //convertible=>1, enum value 1
  test(arr); //convertible=>1, array decay to pointer
  test(s1); //choose false, std::string無法implicit轉型, 也無bool operator
  return 0;
}

上面的nullptr無法implicit轉型bool所以就被歸類在choose false的區域,也剛好nullptr本身的semantics也是bool false

如果因為某些型別需要例外(假如字串要空的才算false)那就可以利用template specialization。 例如:

template <>
bool GetBool<const char*>(const char* s)
{
  std::cout << "choose char" << std::endl;
  return s[0] != '\0';
}
Posted in C++ Language | Leave a comment

software library documentation

整理 https://www.divio.com/blog/documentation/ 對於software documentation的觀點

該篇blog文主要解釋軟體文件需要具備的幾個部分(從他的文章內容來看,這邊的軟體比較接近library)

  1. tutorial
  2. guide (how-to guide)
  3. explanation
  4. reference

一個好的文件,需要區分這四個部分。主要原因在這四個部分所針對的對象不同,目的也不同。

tutorial的重點在new comer,第一次接觸這個軟體/library的人要如何入門。文章提到這常也是軟體是否成功的關鍵-將new comer變成user的關鍵。如果使用的人覺得學習障礙很高,很可能就不用了。. “A bad or missing tutorial will prevent your project from acquiring new users.”

guide是topic based,針對一個topic透過一系列的步驟完成,在文章中他用cook recipe來形容

explanation比較像是背景說明,在一般library文件我們常會看到design rationale可以算在這類

reference主要在information細節整理,可以想成是dictionary

另外文章也說明 區分project documentation和software documentation。有很多文件是算在project documentation:例如change log等

tutorial可以以ALLOW THE USER TO LEARN BY DOING為方向。軟體/library作為工具,要讓使用者了解這是什麼、能達成什麼,最快的方式就是提供範例步驟,儘管很多東西是需要理論背景建構,但是為了能夠先抓住新來使用者的注意,降低學習門檻還是有必要的。因此,範例最好是短、可理解、可即時得到結果。當然,在tutorial裡的範例本身也要有意義才行。

該文章提到,在tutorial裡的重點擺在比較具體的概念,從學習的角度-具體到抽象。並且盡量不做不必要的解釋,在tutorial階段不需要知道的東西不必提及。

guide是goal oriented,針對某個主題或問題(problem),提供一系列的步驟完成解決。用cooking來舉例,完成tutorial的使用者已經懂得基本的料理方法與料理器具的使用。但是煮出某道菜,可能不是那麼直觀,需要將不同已經習得的方法進行整合,在這個階段,也可以假設guide的讀者是有具備一定基礎的使用者,不須針對一些基礎的操作或概念重述

guide強調是步驟而不是概念,解決問題強調的是方法, 如果一個問題有不同的方式處理,可以放在guide說明,而非tutorial。另外,文章提及 “Tutorials need to be complete, end-to-end guides; how-to guides do not.”

tutorial強調的是全面性的介紹,guide強調是特定topic。同時,guide(how-to guide)是要講解 “How to —-” 而非”What is —-“

reference是information-oriented.information-oriented,
不介紹基本概念,並且用詞要精確 “Reference material should be austere and to the point.”

文章提及,reference documentation維持和codebase相同的structure有助於user對照code。 另外也要保持語法用法符號格式的一致性。

reference做的是描述,而非how to或是concept。
“Anything else (explanation, discussion, instruction, speculation, opinion) is not only a distraction, but will make it harder to use and maintain. “

explanation重點在understanding-oriented,可以是discussions,或是提供背景說明,例如為什麼這樣設計library design decisions以及 historical reasons, technical constraints等等。

在該文章最後面說明,事實上,上述的四個面向各有重疊的部分。例如tutorial和how-to guide都描述了一些具體的步驟, reference和explanation都關心比較理論性的部分,而tutorial和explanation都比較適合做為研讀(study)而非實際工作時用到的文件。

文件的形成,常常無法一開始到位。可以採用漸進式的作法,慢慢地往上述四個面向演進。

Posted in General | Leave a comment

js function test if it is called by new operator

在javascript function中,如果要強制使用new,可以參考以下nodejs cluster/worker.js的範例

他這邊利用檢查this是否是instanceof Worker,在一般情況下

但是在new operator下

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new

The new keyword does the following things:
1. Creates a blank, plain JavaScript object;
2. Links (sets the constructor of) this object to another object;
3. Passes the newly created object from Step 1 as the this context;
4. Returns this if the function doesn’t return its own object.

Posted in nodejs | Leave a comment

debug tips

最近在協助debug一個crash的問題,紀錄一下debug的思路。

它的現象是當發soap https request時,在ssl handshake階段就出問題(出現有問題的封包內容或是程式crash)

crash問題比較麻煩的是有時候發生,有時候正常,但一般情況,如果能夠經常重現,比較能夠快一點界定問題。常見的C/C++程式crash出現是在multithread lock問題導致資料的內部結構不一致或毀損。

crash在openssl內部,觀察comp_methods的值,發現內容怪怪的

ssl compression相關的資訊是在初始化就會做完的,第一個合理的猜測:在初始化的時候,可能有多thread同時做ssl init,導致global object的內部結構race condition,所以先在SSL_library_init裡面插printf看看。

這個版本使用的openssl是1.0.2,參考 https://www.openssl.org/docs/man1.0.2/man3/SSL_library_init.html

發現有多個thread呼叫此init函式多次,理論上init只需要一次就好,因此先確定是哪些地方直接或間接呼叫到。這邊用thread來找,當然在環境許可下,也可以直接設breakpoint看callstack

透過callstack先找到解決呼叫多次的問題,

發現ssl init多次是有個lib內部多次呼叫curl_global_init (當然也多次呼叫了curl_global_cleanup) https://curl.haxx.se/libcurl/c/curl_global_init.html

但事實上SSL_library_init被設計成可以呼叫多次(只是不能reentrant,或是thread同時呼叫),參考: https://libwebsockets.org/pipermail/libwebsockets/2016-May/002366.html

所以以上的狀況看起來不是crash問題的原因

SSL_library_init的開始和結尾地方加上printf,觀察thread id,發現其實 SSL_library_init 執行沒有overlap,也就是造成內部結構的問題不在這

前面提到有lib多次呼叫curl global cleanup看起來很可疑,curl_global_cleanup應該只被呼叫一次,而且是在程式最後結束時。因此第二個合理的猜測,應該是在cleanup處將openssl內部global object destroy。

easy.c
vtls/vtls.c
vtls/openssl.c
vtls/openssl.c (openssl 1.0.2: OPENSSL_VERSION_NUMBER 0x100020efL)

果然看到在curl global cleanup裡面呼叫到了SSL_COMP_free_compression_methods,因此前面提到有個lib多次呼叫 curl_global_cleanup ,導致其他地方要使openssl function時因為compression methods內容有問題而crash。

所以這個問題就單純是其中某個lib呼叫的流程時機錯誤,將curl global init/cleanup移到main的一開始和結束就可以了

Posted in Tips | Leave a comment

Linux – (dash)開頭檔名的刪除

產生一個 “-v” 的檔名

echo hello > -v

或是

touch — -v

註 touch -v 不行 -v會被認為是option

列印內容

cat — -v

cat -v 不行,剛好cat -v 是指–show-nonprinting

刪除

rm — -v

rm -v不行 -v會被認為是option

— 的功能主要是告知 option scanning可以結束,在一般unix程式,option的解析是透過getopt()來處理,在getopt() 單獨的 “–” 參數 代表後面不再當成option。當然,這也只適用於程式是用getopt()來解析arguments的情況。

The special argument “–” forces an end of option-scanning regardless of the scanning mode.

http://man7.org/linux/man-pages/man3/getopt.3.html
Posted in System Administration | Leave a comment

CentOS sshd log

sshd log是透過syslog來處理 ,參考 /etc/ssh/sshd_config設定

/etc/ssh/sshd_config

實際上記錄到哪一個檔案可查看rsyslog config /etc/rsyslog.conf

上面說明的是 *.info或是更高權限的log記錄到 /var/log/messages,但是mail, authpriv, cron當成no priority。

authpriv log到 /var/log/secure,所以sshd log在CentOS是記錄到 /var/log/secure

參考: https://www.rsyslog.com/doc/v8-stable/configuration/filters.html#selectors

The keyword none stands for no priority of the given facility.

Posted in System Administration | Leave a comment

更新git

CentOS 7自帶的git 版本是1.8.3,如果要更新可以從source build

https://github.com/git/git/releases

make configure
./configure –prefix=/usr/local
make
make install

安裝完會裝至 /usr/bin/local, 但是如果直接打git –version發現還是舊版的,檢查一下env PATH

看起來搜索路徑是先從 /usr/local/bin沒錯

會發生這個原因主要是 bash cache了git路徑,避免每次使用都要重新在PATH搜索,此時只要rehash就好

另外需注意build環境若沒有curl dev files,build完git會無法使用https

Posted in System Administration | Leave a comment

CentOS 7 disable IPv6

nmtui工具可以設定是否要啟用ipv6,但是關掉後發現dmesg還是一直出現router advertisement failed to add default router的訊息,代表網卡的ipv6還是有在運作(發送router solicitation)

查看 /etc/sysconfig/network-scripts  下網卡的設定

IPV6INIT=no

但是透過

ip addr show dev enp1s0

還是可以看到ipv6的設定

參考上面說明: It is not helpful to add IPV6INIT=no parameter to interfaces that need to disable IPv6. Link local ipv6 can still be seen on that interface.

透過sysctl關掉

再檢查 ip addr show dev 就只剩下ipv4的設定了

Posted in System Administration | Leave a comment

exception safety

整理一下David Abrahams提出的exception safety幾個exception handling level

這在設計function時抱持這個概念非常重要, 因為對使用function的使用者來說, 常常在使用C++的function時,會擔心function失敗時的行為。

使用者角度處理exception handling時,不確定該function到底可能會丟出哪些exception,而造成困擾。

因為可能發生的exception不單是看呼叫的function本身, 該function內部呼叫的函式有可能也會產生exception。 如果真的不確定function可能會丟出哪些exception,最好的方式就是將exception往外傳。

當exception往外傳時,代表function流程中斷了,因此這時候就需要思考function exception safety gurantee

他的概念有點類似資料庫transaction integrity,當transcation failure, 資料庫內容的一致性要達到什麼程度。例如ACID裡的C – consistency類似exception safety的Basic exception safety(invariants在function前後保持一致)

Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct.
參考: https://en.wikipedia.org/wiki/ACID#Consistency

以下參考ˋ整理自 https://en.wikipedia.org/wiki/Exception_safety,並加上補註。同時用一個程式安裝包為例子來描述說明exception safety level概念。

這裡安裝包範例假設是一個如同wix bootstrapper安裝包程式或像是visual studio的安裝包包含了許多component。

exception safety level分成以下四個:

No-throw guarantee, also known as failure transparency:
Operations are guaranteed to succeed and satisfy all requirements even in exceptional situations. If an exception occurs, it will be handled internally and not observed by clients.

這代表該function可以直接handle exception, 並且解決即使在exception的情況下,仍然可以正確地完成要做的事。 function是否能達到no throw guarantee要看function本身的spec, 例如要insert data這種的function,如果memory不足導致std::bad_alloc,那insert肯定是做不下去,也沒有其他處理方式。但是如果function的功能是insert data to cache操作,insert發生memory不足,那直接清掉cache再insert就可以silently succeed完成。所以能否達到no-throw要看function spec。

用安裝包的例子就是當磁碟空間不足時,發生exception,基本上無法處理。

Strong exception safety, also known as commit or rollback semantics:
Operations can fail, but failed operations are guaranteed to have no side effects, leaving the original values intact.

這裡的strong exception safety要求沒有side effect,也就是如果function發生exception,要能夠沒有side effect,狀態回復到function call之前。

用安裝包的例子就是當發生exception(例如磁碟空間不足)就將安裝的component解安裝,大多數的安裝包系統都有rollback處理機制。

理論上C++ function在使用時,可以知道會throw哪些exception,但實際上很少這樣做,文件通常都沒有標註,若我們看不到實現細節時,也無從知道到底function內所使用的其他函式會丟出哪些exceptions。如果僅僅要求basic exception safety這通常不會有太大困擾。

若想要達到no-throw或是strong exception safety就意味著需要明確/精確知道底層會丟出哪些exception,並且確定底層function call的exception level

Basic exception safety, also known as a no-leak guarantee:
Partial execution of failed operations can result in side effects, but all invariants are preserved and there are no resource leaks (including memory leaks). Any stored data will contain valid values which may differ from the original values.

這個大概是設計function/class最基本的要求,因為no exception safety有可能導致resource leak,這是在一般設計不希望發生的。 Basic exception safety要求要保證invariant的正確性 (不能有resource leak其實也是一種invariant: 可以想成在single execution flow下呼叫function前後,resource的狀態是一致的)。但是他不保證沒有side effects,也就是function可能事情做到一半,改變了某些值。

用安裝包的例子就是當發生exception就停止繼續安裝,但是確保已安裝的component資訊都是正確完整的(例如windows installer registry)。所以這時候side effect就是那些已成功安裝的component。 並且可能安裝程式為了避免其他安裝程式同時執行會有mutex機制,該mutex要正確release (例如windows MSI會設置 Global_MSIExecute named mutex避免兩個msi同時執行安裝)

No exception safety: No guarantees are made.

這個是最糟糕的,代表如果function拋出了exception,有可能導致invariant失效,resource leak(例如在function裡new但是沒有delete)。一般情況,如果有follow RAII,可以做到basic exception safety。

用安裝包的例子就是當發生exception不保證任何事情,有可能安裝完成的component registry資訊寫一半。或是installer mutex沒有解除,導致其他安裝程式也無法進行安裝。

Posted in C++ idioms | Leave a comment