Skip to content
Update cpp authored by umaumax's avatar umaumax
[[_TOC_]]
## コーディング時の注意事項
* [CERT C コーディングスタンダード 00\. はじめに]( https://www.jpcert.or.jp/sc-rules/00.introduction.html )
* [Google C\+\+ Style Guide]( https://google.github.io/styleguide/cppguide.html )
### 関数の出力
[Google C\+\+ Style Guide -Inputs_and_Outputs]( https://google.github.io/styleguide/cppguide.html#Inputs_and_Outputs )
* 基本的に戻り値を利用すること
* どうしても引数にする場合
* 入力引数の後に出力引数の順番とする
* 出力/入出力の変数はnon-const pointerを利用すること
e.g. `void Foo(const string &in, string *out);`
なお,入力としてポインタの情報が欲しい場合や`nullptr`である可能性がある場合には,`const T*`を利用しても良い(これは出力に`T&`ではなくポインタを利用するメリットの1つでもある)
[C\+\+ Core Guidelines: The Rules for in, out, in\-out, consume, and forward Function Parameter \- ModernesCpp\.com]( https://www.modernescpp.com/index.php/c-core-guidelines-how-to-pass-function-parameters )
では,`out`なケースは単独でも複数でも返り値として返す方法を採用し,`in-out`の場合はnon-const reference(`T&`)を採用している
* [void foo\(T& out\) \- How to fix output parameters]( https://foonathan.net/2016/10/output-parameter/ )
* [Input\-output arguments: reference, pointers or values? · Mathieu Ropert]( https://mropert.github.io/2018/04/03/output_arguments/ )
個人的には
* ポインタ利用: null pointerチェックがあるので,面倒であるし,バグの温床
* リファレンス利用: 呼び出し側で初期化忘れ防止の初期値が無駄になり,関数内でエラーでの早期returnのときに値を初期値にするのかという問題がでてくる
* 返り値で返す: 実はgoogle coding styleにも合致する方法で,良いとこどりなのではないか?
### 注意点が多く記述されているFAQ
[C\+\+ FAQ]( https://isocpp.org/wiki/faq )
### クラスのstaticフィールドの利用方法
[C\+\+の class template を使えば static メンバの実体がヘッダファイルに書けるカラクリ \- higepon blog]( https://higepon.hatenablog.com/entry/20100803/1280834422 )
#### 定数値をhppではなく、cppファイルに記述する方法
[explicit-define-static-data-mems \- Constructors, C\+\+ FAQ]( https://isocpp.org/wiki/faq/ctors#explicit-define-static-data-mems )
`std::string`はこちら
[c\+\+ \- Static constant string \(class member\) \- Stack Overflow]( https://stackoverflow.com/questions/1563897/static-constant-string-class-member )
#### 定数値をhpp、cppファイルに宣言を記述する方法
[static-const-with-initializers \- Constructors, C\+\+ FAQ]( https://isocpp.org/wiki/faq/ctors#static-const-with-initializers )
### `__`(ダブルアンダースコア)は変数のどこにあっても違反
[syntax \- Why do people use \_\_ \(double underscore\) so much in C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/224397/why-do-people-use-double-underscore-so-much-in-c )
## C++17
* [variant - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/variant/variant.html )
* [optional - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/optional/optional.html )
上記は、[sewenew/redis-plus-plus: Redis client written in C++]( https://github.com/sewenew/redis-plus-plus )にて活用されている
### 🔥directory_iterator(ファイルの走査順序は未規定)
* [directory_iterator - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/filesystem/directory_iterator.html )
* [std::directory_iterator に注意せよ]( https://zenn.dev/enchan1207/articles/29de772131de13 )
## 終了時のエラー回避のために
### 基底クラスが派生クラスのリソースへ依存している際の解放順番に注意
継承でスレッドを利用している例
``` cpp
#include <chrono>
#include <iostream>
#include <thread>
class Base {
public:
Base() { std::cout << "Base constructor" << std::endl; }
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
Join();
}
void Start() {
thread_ = std::thread([this]() { Callback(); });
}
void Join() {
if (thread_.joinable()) {
thread_.join();
}
}
virtual void Callback() = 0;
private:
std::thread thread_;
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor" << std::endl;
message_ = "alive";
}
~Derived() override {
std::cout << "Derived destructor" << std::endl;
// 継承元の明示的なJoin()が必要となる
Join();
message_ = "dead";
}
void Callback() override {
for (int i = 0; i < 10; i++) {
std::cout << "Callback:" << message_ << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
private:
std::string message_;
};
int main() {
Base* b = new Derived();
b->Start();
std::this_thread::sleep_for(std::chrono::milliseconds(300));
delete b;
}
```
``` bash
$ ./a.out
Base constructor
Derived constructor
Callback:alive
Callback:alive
Callback:alive
Derived destructor
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Base destructor
```
### フィールドメンバがスレッド経由でクラスのリソースへ依存している際の解放順番に注意
``` cpp
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>
class Worker {
public:
Worker() { std::cout << "Base constructor" << std::endl; }
virtual ~Worker() {
std::cout << "Worker destructor" << std::endl;
Join();
}
void Start(std::function<void(void)> callback) {
thread_ = std::thread([callback]() { callback(); });
}
void Join() {
if (thread_.joinable()) {
thread_.join();
}
}
private:
std::thread thread_;
};
class Hoge {
public:
Hoge() {
std::cout << "Hoge constructor" << std::endl;
message_ = "alive";
}
~Hoge() {
std::cout << "Hoge destructor" << std::endl;
// フィールド利用の明示的なJoin()が必要となる
worker_.Join();
message_ = "dead";
}
void Start() {
worker_.Start([this]() {
for (int i = 0; i < 10; i++) {
std::cout << "Callback:" << message_ << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
}
private:
Worker worker_;
std::string message_;
};
int main() {
Hoge hoge;
hoge.Start();
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
```
``` bash
$ ./a.out
Base constructor
Hoge constructor
Callback:alive
Callback:alive
Callback:alive
Hoge destructor
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Worker destructor
```
## 継承
public, protected, privateの3種類の継承があるが,通常はpublic継承を利用するので,あまり気にする場面はないと思われる
[非public継承の使いどころ \| 闇夜のC\+\+]( http://cpp.aquariuscode.com/inheritance-use-case )
### 抽象クラスを利用するときには、ポインタとして利用する
抽象クラスはインスタンス化できない
### コンストラクタ/デストラクタの呼び出し順序
``` cpp
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base constructor" << std::endl; // 1
}
virtual ~Base() {
std::cout << "Base destructor" << std::endl; // 6
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor" << std::endl; // 2
}
~Derived() override {
std::cout << "Derived destructor" << std::endl; // 5
}
};
class DoubleDerived : public Derived {
public:
DoubleDerived() {
std::cout << "DoubleDerived constructor" << std::endl; // 3
}
~DoubleDerived() override {
std::cout << "DoubleDerived destructor" << std::endl; // 4
}
};
int main() {
Base* b = new DoubleDerived();
delete b;
}
```
``` bash
$ ./a.out
Base constructor
Derived constructor
DoubleDerived constructor
DoubleDerived destructor
Derived destructor
Base destructor
```
`Base``virtual`を外した場合の挙動
``` bash
$ ./a.out
Base constructor
Derived constructor
DoubleDerived constructor
Base destructor
```
### `virtual`は伝播するので、継承先で明記しなくてもよい
[virtual function specifier \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/virtual )
``` cpp
#include <iostream>
struct Base {
public:
virtual ~Base() { std::cout << "Base\n"; }
};
struct Derived : Base {
public:
/* virtual */ ~Derived() override { std::cout << "Derived\n"; }
};
struct DerivedParent : Derived {
public:
/* virtual */ ~DerivedParent() override { std::cout << "DerivedParent\n"; }
};
int main() {
Base* b = new DerivedParent();
delete b;
}
```
## enum
よく使う命名例
``` cpp
enum Error { kSuccess, kInvalidArgs };
```
## 構造体
### 構造体/クラスのフィールドのコロンで参照bit範囲を設定できる
[c\+\+ \- ":" \(colon\) in C struct \- what does it mean? \- Stack Overflow]( https://stackoverflow.com/questions/8564532/colon-in-c-struct-what-does-it-mean )
* そのフィールドにそれ以上の値を定数で代入すると、ビルド時に警告がある
* 超えた範囲は巡回する(参照bit範囲が固定なので、このように見えるだけでは?)
* 実行時にも有効で、`int v:2;`とすると、`-2,-1,0,1`の値のみを取る
## 関数
### `f(g(x), h(y))`の呼び出し順序は規定されていない
ちなみに、Rustは最近、left-to-rightであることが規定された
## ラムダ関数
* ラムダ関数のキャプチャについて`[&]`は利用しないこと
* 基本的には、`[hoge]`としてコピーするか、引数としする方法を取り、どうしても、参照のキャプチャをしたい場合には明示的に、変数名を指定して`[&hoge]`の形式を利用すること
* `[&this]`はできないので、`[this]`となる
### ローカル変数のキャプチャの遅延実行
* ローカルな変数をキャプチャして他のスレッドに渡して遅延実行はNG
* ダングリングポインタを参照することになり、直後に再帰関数などを呼ぶとスタックの内容が書き換わりやすい
* スレッドごとに、スタックが独立しているので、スレッドによって、破壊の傾向が変化することもある
* スタックの深い場所で発生すると他の箇所からの書き換わりにくいこともあり、発見しにくくなる
* 一見、`-O0`ビルドだとNGで`-O1,-O2,-O3`ビルドだとOKのように見えることがあるが、キャプチャ先のアドレスを参照するのでNG
* `-fstack-protector-all`では検出できない
* キャプチャした後にデータを書き込んでいると、`*** stack smashing detected ***`のような結果となる可能性が高い
* `-fsanitize=address`の場合、他でスタックが伸びて、初めてアクセスがあった場合に実行時に検出される
* 問題がある挙動になって初めて検出されるので、存在しないことの証明ができない
### パフォーマンス
[関数ポインタと関数オブジェクトのインライン展開 \- Qiita]( https://qiita.com/kaityo256/items/5911d50c274465e19cf6 )
関数オブジェクト、関数ポインタ、ラムダ関数の順に遅くなる(この場合、関数ポインタを利用しても速度が同じとなっている)
``` bash
$ g++ -std=c++11 function_object.cpp -O3
$ hyperfine -m 3 ./a.out
Benchmark #1: ./a.out
Time (mean ± σ): 183.4 ms ± 26.3 ms [User: 170.0 ms, System: 5.2 ms]
Range (min … max): 165.5 ms … 232.9 ms 12 runs
$ g++ -std=c++11 funcion_pointer.cpp -O3
$ hyperfine -m 3 ./a.out
Benchmark #1: ./a.out
Time (mean ± σ): 182.9 ms ± 32.0 ms [User: 171.3 ms, System: 5.5 ms]
Range (min … max): 165.5 ms … 260.3 ms 11 runs
$ g++ -std=c++11 funcional.cpp -O3
$ hyperfine -m 3 ./a.out
Benchmark #1: ./a.out
Time (mean ± σ): 1.012 s ± 0.024 s [User: 979.7 ms, System: 8.5 ms]
Range (min … max): 0.996 s … 1.039 s 3 runs
```
## 型
### unsigned char型またはunsigned short型などの演算結果はsigned int型となる
``` cpp
#include <cstdint>
#include <cstdio>
#include <typeinfo>
#define check_type(type) \
{ \
type x = 123; \
if (typeid(x).name() != typeid(~x).name()) { \
printf("%20s type is %s\n", #type, typeid(x).name()); \
printf("%20s type is %s\n", "~" #type, typeid(~x).name()); \
if (typeid(~x).name() != typeid(x << 1).name()) { \
printf("%20s type is %s\n", #type " << 1", typeid(x << 1).name()); \
} \
} else { \
printf("%20s type is %s\n", #type, "same"); \
} \
printf("\n"); \
}
int main(int argc, char* argv[]) {
check_type(unsigned char);
check_type(uint8_t);
check_type(signed char);
check_type(char);
check_type(int8_t);
check_type(unsigned short);
check_type(uint16_t);
check_type(short);
check_type(int16_t);
check_type(uint32_t);
check_type(int32_t);
check_type(unsigned int);
check_type(int);
check_type(uint64_t);
check_type(int64_t);
check_type(bool);
// check_type(float);
// check_type(double);
return 0;
}
```
```
unsigned char type is h
~unsigned char type is i
uint8_t type is h
~uint8_t type is i
signed char type is a
~signed char type is i
char type is c
~char type is i
int8_t type is a
~int8_t type is i
unsigned short type is t
~unsigned short type is i
uint16_t type is t
~uint16_t type is i
short type is s
~short type is i
int16_t type is s
~int16_t type is i
uint32_t type is same
int32_t type is same
unsigned int type is same
int type is same
uint64_t type is same
int64_t type is same
bool type is b
~bool type is i
```
## template
### インスタンス化
例えば、テンプレートを明示的にインスタンス化して実装をcpp側にすることで、通常の関数のように扱うテクニックがある
* [テンプレートのインスタンス化 | Programming Place Plus C++編【言語解説】 第21章]( https://programming-place.net/ppp/contents/cpp/language/021.html )
* [[C++]特殊化?実体化??インスタンス化???明示的????部分的????? - 地面を見下ろす少年の足蹴にされる私]( https://onihusube.hatenablog.com/entry/2020/01/24/183247 )
### 関数の戻り値のテンプレート
ユーザに戻り値を決定させたい場合に、明示的に指定する箇所を最低限にしたい場合は最初に返り値の型を指定させるようにするとよい
``` cpp
template <typename RET_TYPE, typename T1, typename T2>
RET_TYPE sum(T1 a, T2 b) {
return a + b;
}
sum<int>(1, 2);
```
### 後置の戻り値の型と型変換
パラメータが検出されるまで,変数は存在しないため,戻り値の型を後置にしなければならない
``` cpp
template<typename I>
auto func(I beg, I end) -> decltype(*beg) {
// ...
return *beg;
}
```
## 参照
### `&`付きの型に対して,自由に値を代入したい
* [C\+\+ \- 参照の初期化を条件分岐で行う方法について|teratail]( https://teratail.com/questions/158884 )
* [std::vector で参照を保持したい \- Secret Garden\(Instrumental\)]( http://secret-garden.hatenablog.com/entry/2015/08/28/000000 )
``` cpp
#include <functional>
#include <iostream>
#include <string>
#include <vector>
void print_first_one(const std::vector<std::string>& vec) {
std::cout << vec[0] << std::endl;
}
int main(int argc, char* argv[]) {
std::vector<std::string> a = {"alice"};
std::vector<std::string> b = {"bob"};
std::reference_wrapper<std::vector<std::string>> target_ref = a;
std::cout << target_ref.get()[0] << std::endl;
print_first_one(target_ref);
target_ref = b;
std::cout << target_ref.get()[0] << std::endl;
print_first_one(target_ref);
return 0;
}
```
## new
### placement new
[『placement new』自分でメモリを管理してみしょう \| GAMEWORKS LAB]( http://gameworkslab.jp/2020/01/28/%E3%80%8Eplacement-new%E3%80%8F%E8%87%AA%E5%88%86%E3%81%A7%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%92%E7%AE%A1%E7%90%86%E3%81%97%E3%81%A6%E3%81%BF%E3%81%97%E3%82%87%E3%81%86/ )
#### コンストラクタは自動的に呼ばれる
#### デストラクタは明示的に呼び出す必要がある(deleteを呼んではならない)
[c\+\+ \- why destructor is not called implicitly in placement new"? \- Stack Overflow]( https://stackoverflow.com/questions/1022320/why-destructor-is-not-called-implicitly-in-placement-new )
## main関数
[implementation-defined]( https://en.cppreference.com/w/cpp/language/main_function )
> A very common implementation-defined form of main() has a third argument (in addition to argc and argv), of type char*[], pointing at an array of pointers to the execution environment variables.
`envp`は処理系定義
``` cpp
int main(int argc, char *argv[], char *envp[]) { }
```
FYI: [\`main\` function and command-line arguments (C++) | Microsoft Docs]( https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#the-envp-command-line-argument )
## デバッグ
### `__PRETTY_FUNCTION__`
[事前定義識別子\_\_func\_\_ \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/lang/cpp11/func.html )
GCCの言語拡張で、名前空間名、クラス名、戻り値やパラメータといった情報も含む関数の文字列となる
## std::offstream
### error handling
* [c\+\+ \- Error handling in std::ofstream while writing data \- Stack Overflow]( https://stackoverflow.com/questions/28342660/error-handling-in-stdofstream-while-writing-data )
* [c\+\+ \- Get std::fstream failure error messages and/or exceptions \- Stack Overflow]( https://stackoverflow.com/questions/839644/get-stdfstream-failure-error-messages-and-or-exceptions )
__`write`したあとに`flush`して、初めて書き込みが実際に行われて、エラーがどうかがわかる__
例えば、`cgroup``cpu`への`tasks`ファイルへ書き込む際には、書き込むPID,TIDの設定(e.g. スケジューリング)に依存して、エラーとなる可能性があり、書き込む内容に依存した結果となる
## `std::vector`
### 非ゼロ値で初期化したい
``` cpp
std::vector<int> vec = {1, 2, 3};
vec.resize(5, 10);
```
`{1, 2, 3, 10, 10}`
### 確保したバッファを削除したい
`clear()``resize(0)`もキャパシティはそのまま確保された状態になるので、`shrink_to_fit()`で切り詰めないと意図した動作にはならない
### std::remove_if
[remove\_if \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/algorithm/remove_if.html )
削除する要素を詰めて、サイズが縮んだコンテナの新しい終端イテレータを返す仕様であり、要素は削除されないので、サイズは変わらない
### コピーしたい場合
[std vector C\+\+ \-\- deep or shallow copy \- Stack Overflow]( https://stackoverflow.com/questions/11348376/std-vector-c-deep-or-shallow-copy )
* `=`: 各要素の値をdeep copyする
* もし、要素自身がポインタならば意味的にはshallow copyとなっている
* 型が異なる要素のコピーを行う場合は、`std::copy`を利用する
``` cpp
std::vector<uint32_t> vec;// dst
std::vector<int> vec_tmp;// src
vec.resize(vec_tmp.size());
std::copy(std::begin(vec_tmp), std::end(vec_tmp), std::begin(vec));
```
### `std::vector`を関数の引数にそのまま指定できるかどうか
``` cpp
#include <vector>
void f_vec_ref(std::vector<int>& vec) {}
void f_vec(std::vector<int> vec) {}
int main(int argc, char* argv[]) {
std::vector<int> vec;
vec = {0, 1, 2, 3, 4};
f_vec(vec);
f_vec({0, 1, 2, 3, 4});
f_vec_ref(vec);
// NG
// f_vec_ref({0, 1, 2, 3, 4});
return 0;
}
```
### `std::vector<bool>`
[On vector\<bool> \-\- Howard Hinnant : Standard C\+\+]( https://isocpp.org/blog/2012/11/on-vectorbool )
* __自動でビットを利用するように最適化される__(内部的にはアロケータとして`std::allocator<bool>`が利用されている)
* 要素のポインターを取得できない
* `.data()`メソッドが実装されていない
``` cpp
std::vector<bool> v(8);
bool* p = &v[0];
```
```
error: no viable conversion from '__bit_iterator<std::__1::vector<bool,
std::__1::allocator<bool> >, false>' to 'bool *'
```
結局の所,イテレータでのアクセスを前提として利用する形式なので,`int64_t`などの型を利用して効率的に演算は正攻法ではできない
サイズ固定ではない`std::bitset`が欲しい場合には`boost::dynamic_bitset`を利用することになりそう
`boost::dynamic_bitset<uint8_t> bit_data;`として,`bit_data.append(0xFF)`とすれば,規定サイズ単位の数値は簡単に追加できる(上位bit方向(先頭)に追加されることに注意)
逆に,`10bit`単位の数を上記のように作成することはできなさそう
### [std::vector をがんばって高速に resize する \- Qiita]( https://qiita.com/i_saint/items/59c394a28a5244ec94e1 )
## std::regex
### 注意点
``` cpp
if (std::regex_match(std::string("str[i]"), match, re)) {}
```
第1引数はmatchで値を取り出すまで,スコープが有効でなければならないので,上記の例はNG
[regex\_match \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/regex/regex_match.html )
> match_results オブジェクトを引数に取る形式の場合、そのオブジェクトは引数で指定した検索対象文字列へのイテレータを保持する。
このため、検索対象文字列は本関数を呼び出した後も match_results オブジェクトを使用し終わるまで破棄されないようにする必要がある。
## std::shared_ptr
### std::shared_ptrでラップしても、基底クラスに継承クラスを代入可能である
### コピー代入ではなくstd::moveを利用するとパフォーマンスが改善できる
[shared_ptr - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/memory/shared_ptr.html )
> 非スレッドセーフに参照カウントを増減させる方法はない。シングルスレッドでのパフォーマンスが重要で、スレッドセーフであることによるオーバーヘッドが問題になる場合、ムーブを活用すればパフォーマンスを改善できる。
``` diff
- piyo = hoge_shared_ptr;
+ piyo = std::move(hoge_shared_ptr);
```
### ラムダ関数との組み合わせの注意点
[cpp\-examples/pitfalls/shared\_ptr at master · umaumax/cpp\-examples]( https://github.com/umaumax/cpp-examples/tree/master/pitfalls/shared_ptr )
[enable_shared_from_this - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/memory/enable_shared_from_this.html )に関連するネタ?
## `std::execution`
[実行ポリシー \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/execution/execution/execution_policy.html )
`C++17`から有効な機能
例えば、`std::sort`などを並列で実行することができる
## `std::this_thread::yield`
ビジーウェイトとする場合にはこれを呼び出して明示的にCPUを明け渡すと行儀が良い
* [yield \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/thread/this_thread/yield.html )
疑問点: 通常の`sleep()`[std::this\_thread::yield \- cppreference\.com]( https://en.cppreference.com/w/cpp/thread/yield )の例のようなsleepのどちらがよい?(ケース・バイ・ケースだと思われるが,短い時間のsleepならば後者こちらの方が明示的にCPUを明け渡せるのでよいのかも)
## `std::cout`
### `std::cout`を`std::ostream`として扱いたい
[c\+\+11 \- std::ostream to file or standard output \- Stack Overflow]( https://stackoverflow.com/questions/23345504/stdostream-to-file-or-standard-output )
``` cpp
std::ostream(std::cout.rdbuf())
```
### [std::cout 乗っ取り計画 \- Qiita]( https://qiita.com/yohhoy/items/1dce1c0d19baae48ae78 )
`std::cout.rdbuf()`を取得して、カスタムしたものを再度、設定する手順を踏む
## std::chrono
### std::chrono::duration
`std::chrono::milliseconds(duration)``duration`の単位に`double`を利用するとわかりにくいビルドエラーとなることに注意
### std::chrono::system_clock::time_point
``` cpp
#include <chrono>
#include <iostream>
int main(int argc, char* argv[]) {
std::chrono::system_clock::time_point zero{};
std::chrono::system_clock::time_point min =
std::chrono::system_clock::time_point::min();
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
double sub =
std::chrono::duration_cast<std::chrono::milliseconds>(now - zero).count();
std::cout << "zero:" << zero.time_since_epoch().count() << std::endl;
std::cout << "min:" << min.time_since_epoch().count() << std::endl;
std::cout << "now:" << now.time_since_epoch().count() << std::endl;
std::cout << "sub:" << sub << std::endl;
return 0;
}
// zero:0
// min:-9223372036854775808
// now:1589511568463908655
// sub:1.58951e+12
```
### `std::chrono::system_clock::now()`
実装で最終的に呼び出される`clock_gettime`は発行CPUに関わらず、共通の結果となるように見える
[c\+\+ \- Where does the time from \`std::chrono::system\_clock::now\(\)\.time\_since\_epoch\(\)\` come from and can it block if accessed from multiple threads? \- Stack Overflow]( https://stackoverflow.com/questions/46740302/where-does-the-time-from-stdchronosystem-clocknow-time-since-epoch-c/46740824#46740824 )
> A typical C++ standard library implementation would rely on the underlying OS system call to get the actual system clock value to construct the time_point object.
[gcc/chrono\.cc at master · gcc\-mirror/gcc]( https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B11/chrono.cc#L80 )
> syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &tp);
> clock_gettime(CLOCK_MONOTONIC, &tp);
[linuxで時刻を取得する方法 \- pyopyopyo \- Linuxとかプログラミングの覚え書き \-]( https://pyopyopyo.hatenablog.com/entry/20060711/p1 )
各アーキテクチャでの実装例はGolangの実装を見ると良さそう
[Goとrdtscの謎を追う \- Qiita]( https://qiita.com/kubo39/items/4319fa243fd18acc0981 )
[Man page of CLOCK\_GETRES]( https://linuxjm.osdn.jp/html/LDP_man-pages/man2/clock_getres.2.html )
> CLOCK_PROCESS_CPUTIME_ID (Linux 2.6.12 以降)
> プロセス単位の CPU タイムクロック (そのプロセスの全スレッドで消費される CPU 時間を計測する)。
> CLOCK_THREAD_CPUTIME_ID (Linux 2.6.12 以降)
> スレッド固有の CPU タイムクロック。
`CLOCK_MONOTONIC`では、基本的にCPU間の問題は回避できているように見える
## std::map, std::unordered_map
### `[]`アクセス
存在する場合はその値を、存在しない場合はデフォルト値(デフォルトコンストラクタが存在する場合)を返すことに注意
### `insert()`
存在しない場合は追加、存在する場合は無視という挙動となることに注意
### [mapのキーにvectorが使える \- minus9d's diary]( https://minus9d.hatenablog.com/entry/20120610/1339332308 )
`比較演算子`がある必要がある
### [俺のunordered\_mapがこんなにpair型をキーにできないわけがない \- Qiita]( https://qiita.com/ganyariya/items/df35d253726269bda436 )
`ハッシュ可能`である必要がある(第3項目に自作ハッシュ関数を定義すれば良い)
## `std::stol`
* c++
* [stol - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/string/stol.html )
* 例外を投げる
* c
* [Man page of STRTOL]( https://linuxjm.osdn.jp/html/LDP_man-pages/man3/strtol.3.html )
* 例外を投げない
## `std::function`
[std::function のコスト \- Kludge Factory]( https://tyfkda.github.io/blog/2020/03/04/std-function-runtime.html )
### `std::function::target()`
[本の虫: std::functionのtargetの使い方]( https://cpplover.blogspot.com/2010/11/stdfunctiontarget.html )
> std::function経由で呼び出すには、格納されている型を実行時に判断しなければならない。そのため、すでに格納されている型が分かっているのならば、中身を取り出すことで、そのような実行時チェックを、中身を取り出す際の一回で済ませることができる。
``` cpp
auto func = *f.target< int (*)(int) >();
for (int i = 0 ; i != 1000000; ++i) {
func(i);
}
```
[関数ポインタ型を混在させて運用する \- C\+\+ プログラミング]( https://ez-net.jp/article/6A/su9HMTVF/wKanc4EjbZFS/ )
ラムダ関数を`target()`で取り出すうまい方法がない
#### `std::function`の中身が指している関数の同一性の比較
直接、`==`を利用して比較してはならず、`target()`を経由する
基本的に`target()`にて、 __正しい型__ を指定できていることが前提なので,下記のような比較ではなく,どの関数を代入しているかどうかのメタ情報を管理するほうがメンテナンス性がよいと考えられる(C関数ポインタであるか,ラムダ関数であるか,`std::bind`を利用しているかなどによって,この型が変わってしまう)
``` cpp
#include <cassert>
#include <functional>
#include <iostream>
void f() { std::cout << "f" << std::endl; }
void g() { std::cout << "g" << std::endl; }
int main(int argc, char const* argv[]) {
std::function<void(void)> func_f1(f); // or &f
std::function<void(void)> func_f2(f);
std::function<void(void)> func_g(g);
std::function<void(void)> func_lambda(
[]() { std::cout << "lambda" << std::endl; });
// NOTE: target() return the pointer to function pointer
// WARN: if type is not correct, target() return nullptr
assert(func_f1.target<void(void)>() == nullptr);
assert(func_lambda.target<void(void)>() == nullptr);
assert(func_lambda.target<void (*)(void)>() == nullptr);
assert(func_f1.target<void (*)(void)>() != nullptr);
assert(func_f2.target<void (*)(void)>() != nullptr);
assert(func_g.target<void (*)(void)>() != nullptr);
auto func_f1_p = *func_f1.target<void (*)(void)>();
auto func_f2_p = *func_f2.target<void (*)(void)>();
auto func_g_p = *func_g.target<void (*)(void)>();
printf("f:%p\n", &f);
printf("g:%p\n", &g);
printf("func_f1:%p\n", func_f1_p);
printf("func_f2:%p\n", func_f2_p);
printf("func_g:%p\n", func_g_p);
assert(func_f1_p == func_f2_p);
assert(func_f1_p == &f);
assert(func_g_p == &g);
func_f1();
func_f2();
func_g();
func_lambda();
return 0;
}
```
## algorithm header
[mismatch - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/algorithm/mismatch.html )
## glibc
### glibcのソースコードの検索ページ
[bminor/glibc: Unofficial mirror of sourceware glibc repository\. Updated daily\.]( https://github.com/bminor/glibc )
## ヘッダ
### unistd.h
UNIx STanDard Header file
### ヘッダファイルはヘッダとソースのどちらでincludeするべき?
* [Google C\+\+ Style Guide - Include_What_You_Use]( https://google.github.io/styleguide/cppguide.html#Include_What_You_Use )
* [c\+\+ \- Including \#includes in header file vs source file \- Stack Overflow]( https://stackoverflow.com/questions/2596449/including-includes-in-header-file-vs-source-file/2596554 )
必要なファイルでincludeするべきである
理由の一つとしては、そのヘッダをincludeしたときに実装のみで必要となるヘッダも余計にincludeしてしまい、無駄であるから
### 余計なヘッダファイルの見つけ方
余計と思われるヘッダを減らした際に`-c`オプション付きでビルドできるかどうかを基準とする方法がある
* [include\-what\-you\-use/include\-what\-you\-use: A tool for use with clang to analyze \#includes in C and C\+\+ source files]( https://github.com/include-what-you-use/include-what-you-use )
* [include\-what\-you\-useとjenkinsでC/C\+\+プロジェクトから不要な\#includeを洗い出す \- Qiita]( https://qiita.com/tomota-tar-gz/items/985b660e8f3052a387ef )
## 初期化
* [C言語 未初期化変数の罠 \- 気まま研究所ブログ]( https://aonasuzutsuki.hatenablog.jp/entry/2018/12/21/111450 )
* [変数の初期化をサボるな,それから \-Wshadow オプションを使え \- akihiko’s tech note]( https://aki-yam.hatenablog.com/entry/20130718/1374163303 )
### 複雑な処理をした結果のconst初期化
[C++ Core Guidelines]( https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es28-use-lambdas-for-complex-initialization-especially-of-const-variables )
e.g.
``` cpp
const widget x = [&] {
widget val; // assume that widget has a default constructor
for (auto i = 2; i <= N; ++i) { // this could be some
val += some_obj.do_something_with(i); // arbitrarily long code
} // needed to initialize x
return val;
}();
```
### 配列の初期化
``` cpp
#include <array>
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
std::array<int, 800> q; // NG
// std::array<int, 800> q = {}; // OK
for (int i = 0; i < 800; i++) {
if (q[i] != 0) {
std::cout << q[i] << std::endl;
}
}
// initialize by fill
std::fill(q.begin(), q.end(), 0);
return 0;
}
```
デフォルトで不定値
``` bash
1920169263
1651076143
1819894831
100
32
2
4
4
2
```
[Array initialization \- cppreference\.com]( https://en.cppreference.com/w/c/language/array_initialization )
> In C, the braced list of an initializer cannot be empty. C++ allows empty list:
> int a[3] = {0}; // valid C and C++ way to zero-out a block-scope array
> int a[3] = {}; // invalid C but valid C++ way to zero-out a block-scope array
Macで試すとCでも問題なくビルドできた
### structの初期化方法
[C\+\+における構造体の初期化 \| 株式会社きじねこ]( http://www.kijineko.co.jp/node/681 )
`Hoge hoge = {};`でOK
``` cpp
struct A
{
int a;
int b;
};
A a = { };
```
> のようにすれば、すべてのデータメンバをゼロ初期化または省略時初期化(データメンバが利用者定義のコ<ンストラクタを持つクラスの場合)することができます。
> Cの場合は、{ } の中に少なくともひとつの初期値を記述しなければなりませんが、C++ではまったく書かなくてもOKです。
> { } による初期化にせよ、( ) による初期化にせよ、処理系によっては内部的にmemsetを呼び出していることも少なからずあります。
この場合のように,`memset()`では`double``float`など危険である
### memsetで0埋めならばたまたま想定通りになる
[浮動小数点数の内部表現\(IEEE\)]( https://www.k-cube.co.jp/wakaba/server/floating_point.html )
``` cpp
数値:0.0
d = 0.000000
00 00 00 00 00 00 00 00
f = 0.000000
00 00 00 00
```
IEEE 754の規格という前提
### newで初期化子が利用できる
[初期化子リスト \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/lang/cpp11/initializer_lists.html )
e.g.
``` cpp
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
double* buf = new double[9]{};
for (int i = 0; i < 9; i++) {
std::cout << buf[i] << std::endl;
}
return 0;
}
```
### 文字列配列の初期化
[STR11\-C\. 文字列リテラルで初期化される文字配列のサイズを指定しない]( https://www.jpcert.or.jp/sc-rules/c-str11-c.html )
``` cpp
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
const char* name_list[] = {"ab", "cd", "ef", "gh", "ij"};
// or
// const char name_list[5][3+1]
// const char* name_list[5]
std::cout << sizeof("ab") << std::endl; // 2+1
std::cout << sizeof(char*) << std::endl; // 8(x86_64)
std::cout << sizeof(name_list) << std::endl; // 5*sizeof(char*)=5*8
std::cout << sizeof(name_list[0]) << std::endl; // sizeof(char*)=8
for (const char* name : name_list) {
std::cout << name << std::endl;
}
// or use std::string without const
// std::string name_list[] = {"ab", "cd", "ef", "gh", "ij"};
return 0;
}
```
注意
* [Size of character \('a'\) in C/C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/2172943/size-of-character-a-in-c-c )
* [sizeof演算子にまつわるアレコレ \- Qiita]( https://qiita.com/yohhoy/items/a2ab2900a2bd36c31879 )
``` cpp
const char* ok_list[] = {"ab", "cd", "ef", "gh", "ij"};
class Hoge {
const char* ok_list[5] = {"ab", "cd", "ef", "gh", "ij"};
// error: initializer for flexible array member ‘const char* Hoge::ng_list []’
const char* ng_list[] = {"ab", "cd", "ef", "gh", "ij"};
};
```
* [c \- How to initialize a structure with flexible array member \- Stack Overflow]( https://stackoverflow.com/questions/8687671/how-to-initialize-a-structure-with-flexible-array-member )
* [DCL38\-C\. フレキシブル配列メンバには正しい構文を使用する]( https://www.jpcert.or.jp/sc-rules/c-dcl38-c.html )
* [フレキシブル配列メンバをC\+\+で \- 茂加部珈琲店]( http://mocabe.hatenablog.com/entry/2018/05/23/121706 )
* [C言語のフレキシブル配列メンバ(flexible array member)、通称struct hack \| NO MORE\! 車輪の再発明]( https://mem-archive.com/2018/08/10/post-529/ )
* あくまでCの構文?
### `std::atomic`の初期値
* [c\+\+ \- What's the default value for a std::atomic? \- Stack Overflow]( https://stackoverflow.com/questions/36320008/whats-the-default-value-for-a-stdatomic )
* [atomic::コンストラクタ \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/atomic/atomic/op_constructor.html )
* C++17までは不定値
* `{}`で初期化可能
* C++20からは値ゼロで初期化
### グローバル変数/スレッドローカル変数(ゼロ初期化)
[Zero initialization \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/zero_initialization )
> For every named variable with static or thread-local storage duration that is not subject to constant initialization, before any other initialization.
下記のように、明示的に処理化されていないグローバル変数およびスレッドローカル変数はゼロ初期化されるが、明示的に初期化子を記述したほうがわかりやすい
``` cpp
// global variables
double f[3]; // zero-initialized to three 0.0's
int* p; // zero-initialized to null pointer value
std::string s; // zero-initialized to indeterminate value then default-initialized to ""
```
### ローカル変数/クラスメンバー(ゼロ初期化)
[Value initialization \(since C\+\+03\) \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/value_initialization )
``` cpp
int n{}; // scalar => zero-initialization, the value is 0
double f = double(); // scalar => zero-initialization, the value is 0.0
int* a = new int[10](); // array => value-initialization of each element the value of each element is 0
```
デフォルトコンストラクタが実装されていない構造体の場合(明示的にコンストラクタを実装した場合には暗黙的なデフォルトコンストラクタが実装されなくなる)は`{}``()`が利用できなくなる
実質、`()`(C++03)と`{}`(C++11)の初期化に違いはないはず...
`float* ary = new float[1000]();``float* ary = new float[1000]{};`のアセンブリが一致した
FYI: [Aggregate initialization \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/aggregate_initialization )
[Array initialization \- cppreference\.com]( https://en.cppreference.com/w/c/language/array_initialization )
``` cpp
int a[3] = {0}; // valid C and C++ way to zero-out a block-scope array
int a[3] = {}; // invalid C but valid C++ way to zero-out a block-scope array
```
### static変数の初期化はスレッドセーフで行われる
[ブロックスコープを持つstatic変数初期化のスレッドセーフ化 \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/lang/cpp11/static_initialization_thread_safely.html )
> ブロックスコープを持つstatic変数の初期化は、スレッドセーフであることが規定された。
> static変数の初期化が完了するまで、他のスレッドは初期化処理の前で待機する。
### グローバル変数の初期化順
[How do I prevent the “static initialization order problem”?]( https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use )
* 仕様で順番は規定されていない
* 複数ファイルにまたがる場合に、順番を明確にしたい場合は関数経由の呼び出しかgccの拡張機能(`__attribute__((init_priority(101)))`)を利用する
* Mac OS Xのgccでは利用できない
[\_\_attribute\_\_\)\(\(init\_priority\(N\)\)\)\(で、C\+\+のグローバル変数の初期化順序を制御する \- memologue]( https://yupo5656.hatenadiary.org/entry/20070203/p1 )
> C++の規格では、コンパイル単位をまたいだグローバル変数の初期化順序は規定されていませんので、g++に責任はありません。
> この初期化順が不定な問題は、init_priority属性を使うと手軽に解決できます。
> init_priority(N)のNの部分の数字が「優先度」で、101以上、65535以下の数値を書けます。小さい数字を指定した変数が先に初期化されるようになります。
> リンカスクリプトを見る限り、「同じ優先度の変数同士の初期化順は、リンクしてみないとわからない」ことと、スクリプトに KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) が先に書かれていることから、「init_priority指定のない変数と、init_priority(65535) の変数では、後者の初期化の方が先に行われる」こともわかります。
> init_priorityの番号による初期化順の制御は、単一の.so内、および単一の実行ファイル内でのみ効くようですね。
グローバス変数の次に、スレッドローカルな変数が初期化されることを挙動から確認
``` cpp
// 1
Hoge hoge;
// 2
thread_local __attribute__((init_priority(101))) Fuga fuga;
```
#### グローバル変数の初期化順(実験による観測)
どのスコープでも、同一のスコープならば、上の行が優先される
* ファイルスコープ
* `main()`前に初期化される
* `__attribute__((init_priority(101)))`の値が小さい方
* ファイルスコープTLS
* 呼ばれたタイミング(ただし、1つでもファイルスコープTLSが呼ばれたタイミングで、ファイルスコープTLS全体の初期化処理が行われる模様)
* どのファイルスコープTLSが呼ばれたかどうかは関係ない
* 明示的に呼び出していない場合には、一切呼び出されないことに注意
* 関数スコープ
* 呼ばれたタイミング
* 関数スコープTLS
* 呼ばれたタイミング
* 関数スコープの値をどうしても早く初期化したい場合は、ファイルスコープからその関数を呼び出せば良い
* 可能な限り、早く値を初期化したい場合には`__attribute__((init_priority(101)))`を付与したファイルスコープの初期化時に、初期化したい対象を利用すればよい
* `thread_local``__attribute__((init_priority(101)))`を設定可能だが、意味がない
* `__attribute__((init_priority(101)))`は、クラスに対してかつファイルスコープの変数のみに利用可能
なお、macの`g++-11`では、`init_priority`が有効ではなかった
``` cpp
#include <iostream>
class Hoge {
public:
Hoge(int input) { v = input; }
int v;
};
int f(int input);
int v = 0;
// thread_local __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 1
// thread_local __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// thread_local __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 3
// thread_local __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 1
// thread_local __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// thread_local __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 3
// __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 1
// __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 3
// __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 1
// __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 3
// Hoge outer_hoge3(++v); // 1
// Hoge outer_hoge2(++v); // 2
// Hoge outer_hoge1(++v); // 3
Hoge outer_hoge1(++v); // 1
Hoge outer_hoge2(++v); // 2
Hoge outer_hoge3(++v); // 3
int f(int input) {
// can only use ‘init_priority’ attribute on file-scope definitions of objects of class type
//
// static thread_local Hoge inner_hoge3(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge1(++v); // 3
// static thread_local Hoge inner_hoge1(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge3(++v); // 3
// static Hoge inner_hoge3(++v); // 1
// static Hoge inner_hoge2(++v); // 2
// static Hoge inner_hoge1(++v); // 3
static Hoge inner_hoge1(++v); // 1
static Hoge inner_hoge2(++v); // 2
static Hoge inner_hoge3(++v); // 3
// Hoge inner_hoge1(++v); // 1
// Hoge inner_hoge2(++v); // 2
// Hoge inner_hoge3(++v); // 3
std::cout << "f() outer hoge1:" << outer_hoge1.v << std::endl;
std::cout << "f() outer hoge2:" << outer_hoge2.v << std::endl;
std::cout << "f() outer hoge3:" << outer_hoge3.v << std::endl;
std::cout << "f() inner hoge1:" << inner_hoge1.v << std::endl;
std::cout << "f() inner hoge2:" << inner_hoge2.v << std::endl;
std::cout << "f() inner hoge3:" << inner_hoge3.v << std::endl;
return input;
}
int main(int argc, char* argv[]) {
// can only use ‘init_priority’ attribute on file-scope definitions of objects of class type
// static thread_local Hoge inner_hoge3(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge1(++v); // 3
// static thread_local Hoge inner_hoge1(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge3(++v); // 3
// static Hoge inner_hoge3(++v); // 1
// static Hoge inner_hoge2(++v); // 2
// static Hoge inner_hoge1(++v); // 3
static Hoge inner_hoge1(++v); // 1
static Hoge inner_hoge2(++v); // 2
static Hoge inner_hoge3(++v); // 3
// Hoge inner_hoge1(++v); // 1
// Hoge inner_hoge2(++v); // 2
// Hoge inner_hoge3(++v); // 3
std::cout << "outer hoge1:" << outer_hoge1.v << std::endl;
std::cout << "outer hoge2:" << outer_hoge2.v << std::endl;
std::cout << "outer hoge3:" << outer_hoge3.v << std::endl;
std::cout << "inner hoge1:" << inner_hoge1.v << std::endl;
std::cout << "inner hoge2:" << inner_hoge2.v << std::endl;
std::cout << "inner hoge3:" << inner_hoge3.v << std::endl;
return 0;
}
```
#### 確実にグローバル変数の値を取得するためには、関数スコープのグローバル変数を利用すると良い
``` cpp
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include <string>
pid_t get_cached_pid() {
static pid_t pid = []() { return getpid(); }();
return pid;
}
void print_pid(std::string message);
void print_cached_pid(std::string message);
struct PrePID {
PrePID() {
print_pid("pre pid global variable constructor");
print_cached_pid("pre pid global variable constructor");
}
} pre_pid;
pid_t pid = getpid();
struct PostPID {
PostPID() {
print_pid("post pid global variable constructor");
print_cached_pid("pre pid global variable constructor");
}
} post_pid;
void print_pid(std::string message) {
printf("pid at %s:%d\n", message.c_str(), pid);
}
void print_cached_pid(std::string message) {
printf("cached pid at %s:%d\n", message.c_str(), get_cached_pid());
}
int main(int argc, char* argv[]) {
print_pid("main func");
return 0;
}
```
``` bash
pid at pre pid global variable constructor:0
cached pid at pre pid global variable constructor:96839
pid at post pid global variable constructor:96839
cached pid at pre pid global variable constructor:96839
pid at main func:96839
```
## `nullptr`のクラスオブジェクトでメソッドを呼び出してみる
:warning: クラスオブジェクトのポインタを参照する処理(e.g. フィールドを参照する)などが内部で行わなければ問題なく実行できてしまう
``` cpp
#include <iostream>
class Hoge {
public:
void PrintArgValue(int x) { std::cout << "arg x=" << x << std::endl; }
void PrintFieldValue() { std::cout << "field x=" << x_ << std::endl; }
private:
int x_ = 123;
};
int main(int argc, char* argv[]) {
Hoge hoge;
hoge.PrintArgValue(456);
hoge.PrintFieldValue();
Hoge* hoge_nunllptr = nullptr;
hoge_nunllptr->PrintArgValue(789);
(reinterpret_cast<Hoge*>(0))->PrintArgValue(789);
// WARN: segmentation fault
hoge_nunllptr->PrintFieldValue();
return 0;
}
```
## 早見表
* [IEEE 754 演算のルール \- C\+\+ の歩き方 \| CppMap]( https://cppmap.github.io/articles/ieee754-arithmetic/ )
* [C\+\+ 型特性 早見表 \- C\+\+ の歩き方 \| CppMap]( https://cppmap.github.io/articles/type-traits/ )
## ADL(Argument Dependent Lookup: 実引数依存の名前探索)
* [C\+\+ ADLとは|問題と危険性、回避方法(明示的回避と事前回避) \| MaryCore]( https://marycore.jp/prog/cpp/about-adl-avoid-adl/ )
* [Argument Dependent Lookup \| 闇夜のC\+\+]( http://cpp.aquariuscode.com/argument-dependent-lookup )
## unnamed namespace in header file
* `.cpp`でnamespaceを定義する場合は1つであるが、ヘッダファイルの場合は複数の翻訳単位で存在することになることに注意(headerで読み込んだファイルは同一ファイル内であるとみなされるため)
* 慣例として、`detail`を利用する(`impl`, `internal`を利用する例もある)
* `detail`: [tinyformat/tinyformat\.h at master · c42f/tinyformat]( https://github.com/c42f/tinyformat/blob/master/tinyformat.h#L181 )
* `detail`, `impl`: boost header
* 標準ライブラリは`std::__detail::`を利用している
## 文字列
### `std::string(nullptr)`は違反
[c\+\+ \- Assign a nullptr to a std::string is safe? \- Stack Overflow]( https://stackoverflow.com/questions/10771864/assign-a-nullptr-to-a-stdstring-is-safe )
### null文字の扱い
* [serial port \- does read\(\) in c read null character \- Stack Overflow]( https://stackoverflow.com/questions/50625105/does-read-in-c-read-null-character )
* `read()``\0`関係なく一定バイト数読む
* [std::string の途中に null 文字が含まれている場合の処理 \- Secret Garden\(Instrumental\)]( http://secret-garden.hatenablog.com/entry/2016/04/14/004729 )
* 問題なく表示されるが,`char*`として表示すると区切れる
* [c\+\+ \- Copy string data with NULL character inside string to char array \- Stack Overflow]( https://stackoverflow.com/questions/22907430/copy-string-data-with-null-character-inside-string-to-char-array )
## bit
### 10bit単位の数値
:warning: ビットフィールドの以下の性質は処理系定義であるので,移植性を考えると使うべきではないと思われる
[variables \- 10 or 12 bit field data type in C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/29529979/10-or-12-bit-field-data-type-in-c )
``` cpp
struct uint10_t {
uint16_t value : 10;
};
```
これはどのような仕様? => [ビットフィールド \- cppreference\.com]( https://ja.cppreference.com/w/cpp/language/bit_field )
`10bit`で表現できない数値を代入しようとすると警告が出る
```
xxx.cpp:22:11: warning: implicit truncation from 'int' to bit-field changes value from 2040 to
1016 [-Wbitfield-constant-conversion]
x.value = 0xFF << 3;
^ ~~~~~~~~~
1 warning generated.
```
### 10bit単位のデータをバイナリにパックしたい
[c\+\+ \- Efficiently packing 10\-bit data on unaligned byte boundries \- Stack Overflow]( https://stackoverflow.com/questions/34775546/efficiently-packing-10-bit-data-on-unaligned-byte-boundries )?
`64bit`中の下位`40bit`を利用して`10bit`x4個と`8bit`x5個として,まとめて計算して利用しようとしても,バイト単位で演算されて離れた箇所にデータが散らばり意図通りにならない(さらに,endianの問題も発生する)ため,結果として,単純にバイトごとに無理やり演算する方法が安全となり得る
下記のコードだが,シフト演算のみを行っているため,endianは関係ない気がしてきた
``` cpp
#include <bitset>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <vector>
/**
* @brief Pack 10bit data to 8bit array
*
* @param[in] upper_data 10bit data(2~9bit data) head pointer.
* @param[in] lower_data 10bit data(0,1bit data) head pointer.
* @param[in] data_size 10bit data array size
* @param[out] buffer 8bit array buffer head pointer(destination) required: 0 filled
*/
void Pack10bitPerByte(const uint8_t* upper_data, const uint8_t* lower_data,
const size_t data_size, uint8_t* buffer) {
int bit_offset = 0;
for (int i = 0; i < data_size; i++) {
uint32_t upper_offset = bit_offset % 8;
uint32_t lower_offset = (bit_offset + 2) % 8;
uint32_t upper_value = static_cast<uint32_t>(upper_data[i]);
uint32_t lower_value = static_cast<uint32_t>(lower_data[i]) & 0x03;
int byte_offset = bit_offset / 8;
// NOTE: set per byte (align is 2bit)
// NOTE: upper bit of upper data
buffer[byte_offset] |= static_cast<uint8_t>((upper_value >> upper_offset));
// NOTE: lower bit of upper data
if (upper_offset != 0) {
buffer[byte_offset + 1] |=
static_cast<uint8_t>((upper_value << ((8 - upper_offset) % 8)));
}
buffer[byte_offset + 1] |=
static_cast<uint8_t>(lower_value << ((8 - lower_offset) % 8));
bit_offset += 10;
}
}
/**
* @brief Pack 10bit data to 8bit array (only for little endian)
*
* @param[in] upper_data 10bit data(2~9bit data) head pointer.
* @param[in] lower_data 10bit data(0,1bit data) head pointer.
* @param[in] data_size 10bit data array size
* @param[out] buffer 8bit array buffer head pointer(destination)
*/
void Pack10bitPer40Bit(const uint8_t* upper_data, const uint8_t* lower_data,
const size_t data_size, uint8_t* buffer) {
int dst_offset = 0;
for (int i = 0; i < data_size;) {
int bit_offset = 0;
uint64_t tmp_calc_space = 0;
int pack_calc_max_n = (40 / 10);
pack_calc_max_n =
pack_calc_max_n < (data_size - i) ? pack_calc_max_n : (data_size - i);
for (int j = 0; j < pack_calc_max_n; i++, j++) {
uint64_t input_value = (static_cast<uint64_t>(upper_data[i]) << 2) |
(static_cast<uint64_t>(lower_data[i] & 0x03));
// NOTE: big endian
// int dst_bit_offset = bit_offset;
// NOTE: little endian
int dst_bit_offset = (40 - 10) - bit_offset;
tmp_calc_space |= input_value << dst_bit_offset;
bit_offset += 10;
}
// NOTE: for big endian
// for (int j = 0; j < bit_offset; j += 8) {
// NOTE: for little endian
for (int j = (bit_offset + (8 - 1)) / 8 * 8 - 8; j >= 0; j -= 8) {
buffer[dst_offset] = static_cast<uint8_t>((tmp_calc_space >> j));
dst_offset++;
}
}
}
int main(int argc, char* argv[]) {
std::vector<uint8_t> upper_data = {0x0F, 0x0F, 0x0F, 0x0F};
std::vector<uint8_t> lower_data = {0, 1, 2, 3};
std::vector<uint8_t> data(5);
std::vector<uint8_t> expected_data = {0x0f, 0x03, 0xd0, 0xf8, 0x3f};
int bit_offset = 0;
int byte_offset = 0;
int size = 4;
std::cout << "Pack10bitPerByte" << std::endl;
Pack10bitPerByte(upper_data.data(), lower_data.data(), size, data.data());
for (auto&& v : data) {
std::cout << (int)v << std::endl;
}
// NOTE: test
for (int i = 0; i < expected_data.size(); i++) {
if (data[i] != expected_data[i]) {
std::cout << "[" << i << "] data:" << (int)data[i]
<< "!= expected_data:" << (int)expected_data[i] << std::endl;
}
}
std::fill(data.begin(), data.end(), 0);
std::cout << "Pack10bitPerByte" << std::endl;
Pack10bitPer40Bit(upper_data.data(), lower_data.data(), size, data.data());
for (auto&& v : data) {
std::cout << (int)v << std::endl;
}
// NOTE: test
for (int i = 0; i < expected_data.size(); i++) {
if (data[i] != expected_data[i]) {
std::cout << "[" << i << "] data:" << (int)data[i]
<< "!= expected_data:" << (int)expected_data[i] << std::endl;
}
}
std::ofstream dst;
dst.open("output.raw", std::ios::out | std::ios::binary | std::ios::trunc);
if (!dst) {
std::cerr << "failed to write file: " << std::endl;
return 1;
}
dst.write((const char*)data.data(), data.size());
return 0;
}
```
ちなみに,`Doxygen`コメントの`in`,`out`は概念上の考え方に近い?(例えば,`&`での参照渡しを考慮したときに,それ自身の値が書き換わる場合と,そのポインタが指し示す値が書き換わる場合がある)
[documentation \- Is that an in or in/out parameter? Doxygen, C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/47732665/is-that-an-in-or-in-out-parameter-doxygen-c )
## シフト演算
* :warning: 意図せずにオーバーフローを引き起こす可能性がある(特に,定数値に対して処理を行う場合 `0xFF << x`とする際に,`uint32_t`の範囲を超えてしまう可能性があり得る)
* :warning: [INT34\-C\. 負のビット数のシフトやオペランドのビット数以上のシフトを行わない]( https://www.jpcert.or.jp/sc-rules/c-int34-c.html )
* 特に,__ビット数以上のシフト__が未定義であることに注意(ビット数以上のシフトを行うことで,`0`となることを意図するような設計に注意)
``` cpp
#include <cassert>
int main(int argc, char* argv[]) {
// NOTE: NG case: overflow(32bit << any bit)
assert(0x000000000 == (0xF0000000 << 4));
assert(0x000000000 == (0xF0000000 << 4UL));
assert(0x000000000 == (0xF0000000 << static_cast<int8_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint8_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<int16_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint16_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<int32_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint32_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<int64_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint64_t>(4)));
// NOTE: OK case: 64bit << any bit
assert(0xF00000000 == (0xF0000000UL << 4));
assert(0xF00000000 == (0xF0000000UL << 4UL));
assert(0xF00000000 == (0xF0000000UL << static_cast<int8_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint8_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<int16_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint16_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<int32_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint32_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<int64_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint64_t>(4)));
return 0;
}
```
## 64bit/32bitマシン別の型サイズ例
``` cpp
#include <cinttypes>
#include <iostream>
#include <string>
#define SIZEOF_PRINT(x) \
std::cout << "" #x << ":" << sizeof(x) << "B" << std::endl;
int main(int argc, char* argv[]) {
SIZEOF_PRINT(int);
SIZEOF_PRINT(long);
SIZEOF_PRINT(unsigned long);
SIZEOF_PRINT(unsigned long long);
SIZEOF_PRINT(uint64_t);
return 0;
}
```
```
# 64bitマシン
int:4B
long:8B
unsigned long:8B
unsigned long long:8B
uint64_t:8B
# 32bitマシン
int:4B
long:4B
unsigned long:4B
unsigned long long:8B
uint64_t:8B
```
## hash
intのhashのinputとoutputが一致してしまう(余計な演算なしにhashの定義を満たす)ため、`std::to_string()`をかますと意図する結果が得られる
* [c++ - Why std::hash\<int> seems to be identity function - Stack Overflow]( https://stackoverflow.com/questions/38304877/why-stdhashint-seems-to-be-identity-function )
* [c++ - hash value of int is the same number - Stack Overflow]( https://stackoverflow.com/questions/19734875/hash-value-of-int-is-the-same-number )
``` cpp
#include <functional> // for std::hash
#include <iostream>
#include <string>
int main() {
std::hash<std::string> string_hash;
for (int i = 0; i < 10; i++) {
std::cout << "" << i << ":" << string_hash(std::to_string(i)) << std::endl;
}
std::hash<int> int_hash;
for (int i = 0; i < 10; i++) {
std::cout << "" << i << ":" << int_hash(i) << std::endl;
}
}
```
```
0:10408321403207385874
1:11413460447292444913
2:17472595041006102391
3:11275350073939794026
4:2169371982377735806
5:16141698810441253349
6:10103374131519304989
7:5566429635965498611
8:14908220028612439606
9:8705150437960110905
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9
```
## [c++11 - golang-style "defer" in C++ - Stack Overflow]( https://stackoverflow.com/questions/33050620/golang-style-defer-in-c )
``` cpp
#include <iostream>
#include <memory>
using defer = std::shared_ptr<void>;
int main() {
std::cout << "start" << std::endl;
defer _(nullptr, [](...) { std::cout << "Hello, World!" << std::endl; });
// or
// std::shared_ptr<void> defer(nullptr, [](...) { std::cout << "Hello, World!" << std::endl; });
// or
// std::shared_ptr<void> defer(nullptr, [](void*) { std::cout << "Hello, World!" << std::endl; });
// or
// std::unique_ptr<void, std::function<void(void*)>> defer(nullptr, [](void*) { std::cout << "Hello, World!" << std::endl; });
std::cout << "end" << std::endl;
return 0;
}
```
`...`は可変長引数であるが、直接`void*`を指定してもよい
[Variadic functions \- cppreference\.com]( https://en.cppreference.com/w/cpp/utility/variadic )
`std::unique_ptr`を利用する場合には、明示的に関数の型を記述する必要がある
## C
### `C`では,関数宣言にて`foo()`ではなく,`foo(void)`とする(`C++`では特に問題はない)
* [【C言語】引数なしの関数には void を書いた方がよいという話 \- 0x19f \(Shinya Kato\) の日報]( https://0x19f.hatenablog.com/entry/2019/04/17/213231 )
* [DCL20\-C\. 引数を受け付けない関数の場合も必ず void を指定する]( https://www.jpcert.or.jp/sc-rules/c-dcl20-c.html )
### [Cのプログラムでたまに出てくる関数内の無意味なvoid宣言の意味 \- $ cat /var/log/shin]( https://shin.hateblo.jp/entry/2013/03/13/175059 )
## 実現したいことからの逆引き
### 外部コマンドを安全に実行したい
[cpp\-examples/examples/fork\-exec at master · umaumax/cpp\-examples]( https://github.com/umaumax/cpp-examples/tree/master/examples/fork-exec )
### c++のコードの概要を知りたい
[Doxygen · Wiki · umaumax / memo · GitLab]( https://gitlab.com/umaumax/memo/-/wikis/Doxygen )
### vectorやmapなどのコレクションをダンプしたい
e.g. [neostream.hpp]( https://gist.github.com/umaumax/9765462a4a7e85dcb52515b7921191fd )
### 標準入出力先が端末かどうかを調べたい
``` cpp
#include <unistd.h>
#include <iostream>
int main() {
const bool stdin_console = isatty(STDIN_FILENO);
const bool stdout_console = isatty(STDOUT_FILENO);
const bool stderr_console = isatty(STDERR_FILENO);
std::cout << "stdin is" << (stdin_console ? "" : " not") << " terminal" << std::endl;
std::cout << "stdout is" << (stdout_console ? "" : " not") << " terminal" << std::endl;
std::cout << "stderr is" << (stderr_console ? "" : " not") << " terminal" << std::endl;
}
```
## 落とし穴(pitfall)
### [cos と std::cos は別物だっていう話 \- akihiko’s tech note]( https://aki-yam.hatenablog.com/entry/20080826/1219757584 )
* リンクの通り,`::cos(float)``double`となっている
* `using namespace std;`の有無で挙動が変化するので注意
* `x86``arm`(32bit)で`std::cos(float)`の計算結果が異なる
``` cpp
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <string>
#include <typeinfo>
#define FORMAT(msg, value) \
msg << (value) << " " << typeid(value).name() << std::endl
inline void test(float f_rad) {
double d_rad = static_cast<double>(f_rad);
std::cout << std::fixed
<< std::setprecision(std::numeric_limits<double>::max_digits10 + 1)
<< "float =" << f_rad << std::endl
<< "double =" << d_rad << std::endl
<< FORMAT("::cos(float) =", ::cos(f_rad))
<< FORMAT("::cos(double) =", ::cos(d_rad))
<< FORMAT("std::cos(float) =", std::cos(f_rad))
<< FORMAT("std::cos(double)=", std::cos(d_rad))
<< FORMAT("cos(float) =", cos(f_rad))
<< FORMAT("cos(double) =", cos(d_rad));
{
using namespace std;
std::cout << std::fixed
<< std::setprecision(std::numeric_limits<double>::max_digits10 + 1)
<< "using namespace std;" << std::endl
<< FORMAT("cos(float) =", cos(f_rad))
<< FORMAT("cos(double) =", cos(d_rad));
}
}
int main(int argc, char* argv[]) {
std::cout << "[test case]: 0.5235987902f" << std::endl;
test(0.5235987902f);
std::cout << std::endl;
std::cout << "[test case]: std::stof(\"0.5235987902\")" << std::endl;
test(std::stof("0.5235987902"));
return 0;
}
```
x86
``` log
[test case]: 0.5235987902f
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025388240814209 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025388240814209 f
cos(double) =0.866025396499206845 d
[test case]: std::stof("0.5235987902")
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025388240814209 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025388240814209 f
cos(double) =0.866025396499206845 d
```
arm
``` log
[test case]: 0.5235987902f
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
[test case]: std::stof("0.5235987902")
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
```
``` diff
--- x86.txt 2021-02-01 18:08:12.539836723 +0900
+++ arm.txt 2021-02-01 18:08:15.675867962 +0900
@@ -3,12 +3,12 @@
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
-std::cos(float) =0.866025388240814209 f
+std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
-cos(float) =0.866025388240814209 f
+cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
[test case]: std::stof("0.5235987902")
@@ -16,10 +16,10 @@
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
-std::cos(float) =0.866025388240814209 f
+std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
-cos(float) =0.866025388240814209 f
+cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
```
### 暗黙的なbool変換について
`-Wall -Wextra -Wconversion`としても、warningとならない
``` cpp
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
// trueなので注意(アドレス値が!=0であるので)
// NG
bool x = []() { return false; };
// OK
// bool x = []() { return false; }();
std::cout << x << std::endl;
return 0;
}
```
### 暗黙的な型変換(float, double)
``` bash
g++ -std=c++11 a.cpp -Wall -Wextra -Wconversion
```
* `std::copy`を利用する場合、`std::vector<float>``std::vector<double>`では警告がでない
* `float``double`へ代入する際に警告がでないが、ユーザとしてはもともとdoubleで計算した結果を意図しているケースが多い
``` cpp
#include <iterator>
#include <vector>
int main(int argc, const char* argv[]) {
(void)&argc;
(void)&argv;
{
std::vector<float> f_vec = {1.0f, 2.0f};
std::vector<double> d_vec = {0.0, 0.0};
// no warning
std::copy(f_vec.begin(), f_vec.end(), d_vec.begin());
// no warning
std::copy(d_vec.begin(), d_vec.end(), f_vec.begin());
}
{
float f = 1.0f;
// 厳密には、ここで桁落ちしているわけではないので警告がでないと思われる
// しかし、意図していない操作である
double d = f;
// warning: conversion to ‘float’ from ‘double’ may alter its value [-Wfloat-conversion]
float f2 = d;
(void)&f;
(void)&d;
(void)&f2;
}
{
// warning: conversion to ‘float’ alters ‘double’ constant value [-Wfloat-conversion]
float f = 1.0f / 3.0;
(void)&f;
}
return 0;
}
```
## ファイル
### ファイルIOのベンチマーク
[c++ io benchmark]( https://gist.github.com/umaumax/1bde594a831c50f0d6d21a520209aad8 )
### ファイル/ディレクトリの存在確認
``` cpp
#include <fstream>
#include <string>
bool IsFileExist(const std::string &filename) {
std::ifstream ifs(filename);
return ifs.is_open();
}
```
* ファイル/ディレクトリが存在: true
* シンボリックリンクの場合はその参照先に依存
### 🌟ファイルを読みたい
* [How to read a file from disk to std::vector\<uint8\_t> in C\+\+]( https://gist.github.com/looopTools/64edd6f0be3067971e0595e1e4328cbc )
* [c++ - How to read a binary file into a vector of unsigned chars - Stack Overflow]( https://stackoverflow.com/questions/15138353/how-to-read-a-binary-file-into-a-vector-of-unsigned-chars )
* __なぜか、`std::istream_iterator`を利用すると、ファイルの読み込みサイズが小さくなってしまう現象が発生した__
* __理由は`\n`をスキップして`std::vector`へ保存してしまうためである__
* `std::istreambuf_iterator`には`uint8_t`は指定できない
``` cpp
#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
// LoadFileGood_istreambuf_iteratorと比較して、2倍ほど遅い
std::vector<uint8_t> LoadFileGood(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary);
if (file.fail()) {
return std::vector<uint8_t>();
}
file.unsetf(std::ios::skipws);
std::streampos fileSize;
file.seekg(0, std::ios::end);
fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> vec;
vec.reserve(fileSize);
vec.insert(vec.begin(), std::istream_iterator<uint8_t>(file),
std::istream_iterator<uint8_t>());
return vec;
}
// 🌟おすすめ
std::vector<uint8_t> LoadFileGood_istreambuf_iterator(const std::string& filepath) {
std::ifstream is(filepath, std::ios::in | std::ios::binary);
std::istreambuf_iterator<char> start(is), end;
std::vector<uint8_t> content(start, end);
return content;
}
std::vector<uint8_t> LoadFileBad_istream_iterator(const std::string& filepath) {
std::ifstream is(filepath, std::ios::in | std::ios::binary);
std::istream_iterator<char> start(is), end;
std::vector<uint8_t> content(start, end);
return content;
}
int main(int argc, const char* argv[]) {
std::string filepath("./main.cpp");
{
auto goodVec = LoadFileGood(filepath);
std::cout << goodVec.size() << std::endl;
for (auto& v : goodVec) {
std::cout << v;
}
std::cout << std::endl;
}
{
auto goodVec = LoadFileGood_istreambuf_iterator(filepath);
std::cout << goodVec.size() << std::endl;
for (auto& v : goodVec) {
std::cout << v;
}
std::cout << std::endl;
}
{
auto badVec = LoadFileBad_istream_iterator(filepath);
std::cout << badVec.size() << std::endl;
for (auto& v : badVec) {
std::cout << v;
}
std::cout << std::endl;
}
return 0;
}
```
### ファイルをmv(rename)する
* [std::rename \- cppreference\.com]( https://en.cppreference.com/w/cpp/io/c/rename )
* [FIO10\-C\. rename\(\) 関数の使用に注意する]( https://www.jpcert.or.jp/sc-rules/c-fio10-c.html )
移植性を考慮した場合,変更先が空ディレクトリは削除できるのでrename時にエラーが起こらないが,空ディレクトリではない場合はremoveが失敗する
``` cpp
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
int main() {
char oldname[] = "a.txt";
char newname[] = "b.txt";
// NOTE:
// remove file for avoiding std::rename implementation-defined
// If new_filename exists, the behavior is implementation-defined.
bool is_exist_newname_file = [](const std::string& filename) {
std::ifstream ifs(filename);
return ifs.is_open();
}(newname);
if (is_exist_newname_file) {
int remove_result = std::remove(newname);
if (remove_result != 0) {
std::cerr << "Error remove file '" << newname
<< "': " << std::strerror(errno) << std::endl;
return 1;
}
}
int rename_result = rename(oldname, newname);
if (rename_result != 0) {
std::cerr << "Error renaming file '" << oldname << "' to '" << newname
<< "': " << std::strerror(errno) << std::endl;
return 1;
}
return 0;
}
```
### ディレクトリ名取得/ディレクトリのfsyncを行う例
``` cpp
#include <errno.h> // strerror
#include <fcntl.h> // O_DIRECTORY, O_RDONLY
#include <unistd.h> // fsync, close
#include <cstring> // strerror
#include <iostream>
#include <string>
#include <tuple>
std::string Dirname(const std::string& filepath) {
const size_t pos = filepath.find_last_of("/");
if (std::string::npos == pos) {
return ".";
}
const std::string dirname = filepath.substr(0, pos);
if (dirname == "") {
return "/";
}
return dirname;
}
std::tuple<bool, std::string> SyncDir(const std::string& dirpath) {
std::string error_message{};
int fd = open(dirpath.c_str(), O_DIRECTORY | O_RDONLY);
if (fd == -1) {
error_message = std::string(strerror(errno)) + ": dirpath = " + dirpath;
return std::make_tuple(false, error_message);
}
int fsync_ret = fsync(fd);
if (fsync_ret == -1) {
error_message += std::string(strerror(errno)) + ": dirpath = " + dirpath;
}
int close_ret = close(fd);
if (close_ret == -1) {
error_message += std::string(strerror(errno)) + ": dirpath = " + dirpath;
}
return std::make_tuple(error_message.empty(), error_message);
}
int main(int argc, char* argv[]) {
bool ret{};
std::string error_message{};
std::tie(ret, error_message) = SyncDir("target_dir");
if (!ret) {
std::cerr << error_message << std::endl;
}
return 0;
}
```
### ファイルディスクリプタ
`<unistd.h>`
| プリプロセッサシンボル | value |
| -------- | -------- |
| STDIN_FILENO | 0 |
| STDOUT_FILENO | 1 |
| STDERR_FILENO | 2 |
[Man page of STDIN]( https://linuxjm.osdn.jp/html/LDP_man-pages/man3/stdin.3.html )
### 一時ファイル作成のイディオム
`open()`してから`unlink()`することで,削除し忘れを防ぐことができる
[ファイルを open したまま各種システムコールを呼び出すとどうなるか \| ゴミ箱]( https://53ningen.com/file-open-rename-unlink/ )
### `close(2)`
[【C言語】close\(\)失敗時にリトライするのは危険という話 \- Qiita]( https://qiita.com/_ydah/items/c5f6b42dbfb88c2fae1f )
### ファイルをseekして使い続ける例
``` cpp
ofs.clear();
ofs.seekg(0, std::ios::beg);
```
`std::ifstream`をずっと繰り返していると、メモリリークがあるのかもしれないと考えての実験用だが,特に問題なし
``` cpp
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
int main(int argc, char* argv[]) {
std::ifstream ifs("./main.cpp");
const int buf_size = 80;
char line[buf_size];
while (true) {
ifs.getline(line, buf_size);
ifs.clear();
ifs.seekg(0, std::ios::beg);
}
return 0;
}
```
## リストに処理をした結果に対して、何らかの処理をするが、共通する結果に対しては1回でまとめてもよい場合の例
``` cpp
#include <iostream>
#include <set>
#include <string>
#include <vector>
int main(int argc, char *argv[]) {
std::vector<std::string> hoge_list = {
"hoge",
"fuga",
"hoge",
};
std::set<std::string> hoge_set = {};
for (auto &&hoge : hoge_list) {
hoge_set.insert(hoge);
}
for (auto &&hoge : hoge_set) {
std::cout << hoge << std::endl;
}
return 0;
}
```
## ODR(one definition rule)
* ヘッダに実装を記述する際に,通常の関数は`inline`を付与する必要がある
* template関数はinline不要
* ただし,特殊化されたtemplateではinlineは必要
## assert
[ERR06\-C\. assert\(\) と abort\(\) の終了動作を理解する]( https://www.jpcert.or.jp/sc-rules/c-err06-c.html )
* `assert()``aboort()`を呼び出して、`SIGABRT`を送信するので、gdbで補足できる
* `NDEBUG`有効時にも、`gdb`でbacktraceを表示したい場合は`abort()`を利用すれば良い
## backtrace
[\[Linux\]\[C/C\+\+\] backtrace取得方法まとめ \- Qiita]( https://qiita.com/koara-local/items/012b917111a96f76d27c )
* glibc backtraceは`-ggdb3`では関数名が出力できず、`-rdynamic`を付加して動的ライブラリ化する必要があった
* 行数までは取得できない
### libunwind
``` bash
# for ubuntu
sudo apt-get install libunwind-dev
```
libunwindを利用すると、`-ggdb3``-rdynamic`なしでも関数名を取得可能
Mac OS Xではclangに取り込まれているので、そのまま利用可能(`-lunwind`も不要)
see: [llvm\-project/libunwind\.h at main · llvm/llvm\-project]( https://github.com/llvm/llvm-project/blob/main/libunwind/include/libunwind.h )
## clang++
### Ubuntuにてclang++で`cannot find iostream`となるときに、下記で直る
[c++ - clang++ cannot find iostream - Ask Ubuntu]( https://askubuntu.com/questions/1449769/clang-cannot-find-iostream )
``` bash
sudo apt install -y g++-12
```
## clang-format
ヘッダのソートなどの優先順位についての例: [clang-formatで*.generated.hを勝手にソートさせない方法]( https://zenn.dev/sgthr7/articles/14271d56253e7a )
## switchのcase中に変数宣言をするときの注意
caseごとではなく、switch単位でスコープとなるので、switchの範囲外で予め宣言するか、case内部に明示的にブロックを設ける必要がある
* [c\+\+ \- Getting a bunch of crosses initialization error \- Stack Overflow]( https://stackoverflow.com/questions/11578936/getting-a-bunch-of-crosses-initialization-error/11578973 )
* [c\+\+ \- What are the signs of crosses initialization? \- Stack Overflow]( https://stackoverflow.com/questions/2392655/what-are-the-signs-of-crosses-initialization )
## コピーやムーブをした場合でもデストラクタが呼ばれる
``` cpp
{
Hoge hoge;// コンストラクタ
hoge = Hoge("hello");// コンストラクタ, デストラクタ
// デストラクタ
}
```
[自作クラスをムーブする \- Qiita]( https://qiita.com/termoshtt/items/3397c149bf2e4ce07e6c )
## 実行時(共有ライブラリのロード時)に`undefined symbol: _ZTI8XXXClass(typeinfo for XXXClass)`
``` cpp
class XXXClass {
virtual void foo() = 0;
};
```
ここで`=0`としない場合は実体を定義する必要がある
ビルドは問題なく通り、実行時に発覚することが問題点
たしかに、共有ライブラリを`nm`コマンドで見てみると`U`となっている
* [c\+\+ \- What is an undefined reference/unresolved external symbol error and how do I fix it? \- Stack Overflow]( https://stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix )
* [Undefined symbols for architecture x86\_64:の原因(複数) \| MaryCore]( https://marycore.jp/prog/xcode/undefined-symbols-for-architecture-x86-64/ )
## 読み物
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(1\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040712/p1 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(2\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040712/p2 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(3\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040715/p1 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(4\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040724/p1 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(5\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040725/p2 )
## tips
### std::endl
`std::endl`は実行毎に出力バッファの内容を逐一flushするので、実行時オーバーヘッドを減らすため、ループの途中などflushの必要がない箇所では改行文字を使用した方が良い。
``` cpp
for (auto i = start; i != end; ++i) std::cout << i << std::endl;
// =>
for (auto i = start; i != end; ++i) std::cout << i << '\n';
```
### std::map
`std::map`系のコンテナに対して`[]`演算子を用いて存在しないキーを参照した場合、valueをデフォルト値で初期化した上で要素を新規作成するという挙動である
### 出力用の参照渡し引数ではなく、返り値で出力を返すことが推奨される
``` cpp
void get_pos(Pos &out_pos) {
out_pos.x = 1;
out_pos.y = 2;
}
```
=>
``` cpp
struct Pos {
int x, y;
};
Pos get_pos() {
return {1, 2};
}
```
戻り値の構造体は自動的にmoveされるので、実行時オーバーヘッドは発生しない。
理想的には、RustのようにResult型を利用すると良い(ただし、デファクトスタンダードなライブラリが不明...)
### 基底クラスとなるクラスのデストラクタにはvirtualを付与すること
付与しないと継承先クラスを基底クラスへキャストしたときに呼び出されない
``` cpp
#include <iostream>
class Animal {
public:
void foo() { std::cout << "do foo" << std::endl; }
~Animal() { std::cout << "called base animal destructor" << std::endl; }
};
class Dog : public Animal {
public:
~Dog() { std::cout << "called derived dog destructor" << std::endl; }
};
class VirtualAnimal {
public:
void foo() { std::cout << "do virtual foo" << std::endl; }
virtual ~VirtualAnimal() {
std::cout << "called base virtual animal destructor" << std::endl;
}
};
class VirtualDog : public VirtualAnimal {
public:
~VirtualDog() {
std::cout << "called derived virtual dog destructor" << std::endl;
}
};
int main(int argc, const char* argv[]) {
std::cout << "start" << std::endl;
std::cout << "# dog" << std::endl;
{
Dog dog;
dog.foo();
}
std::cout << "# virtual dog" << std::endl;
{
VirtualDog dog;
dog.foo();
}
std::cout << "# derived dog" << std::endl;
{
auto derived = std::make_unique<Dog>();
std::unique_ptr<Animal> p = std::move(derived);
p->foo();
}
std::cout << "# derived virtual dog" << std::endl;
{
auto derived = std::make_unique<VirtualDog>();
std::unique_ptr<VirtualAnimal> p = std::move(derived);
p->foo();
}
std::cout << "end" << std::endl;
return 0;
}
```
``` cpp
start
# dog
do foo
called derived dog destructor
called base animal destructor
# virtual dog
do virtual foo
called derived virtual dog destructor
called base virtual animal destructor
# derived dog
do foo
called base animal destructor <=== 🔥 継承先のデストラクタが呼び出されていないことがわかる
# derived virtual dog
do virtual foo
called derived virtual dog destructor
called base virtual animal destructor
end
```
### 🔥クラスのメンバ変数はコンストラクタ引数の順序ではなく、メンバの定義順に初期化される
``` cpp
#include <iostream>
class ViewGood {
public:
ViewGood(int *start, std::size_t size)
: m_start{start}, m_end{m_start + size} {
std::cout << "view constructor: " << m_start << "," << m_end << '\n';
}
private:
int *m_start;
int *m_end;
};
class ViewBad {
public:
ViewBad(int *start, std::size_t size)
: m_start{start}, m_end{m_start + size} {
std::cout << "view constructor: " << m_start << "," << m_end << '\n';
}
private:
int *m_end;
int *m_start;
};
int main(int argc, const char *argv[]) {
{ ViewGood v(nullptr, 1); }
{ ViewBad v(nullptr, 1); }
return 0;
}
```
``` bash
$ g++ -std=c++20 main.cpp
main.cpp:18:31: warning: field 'm_start' is uninitialized when used here [-Wuninitialized]
: m_start{start}, m_end{m_start + size} {
^
$ ./a.out
view constructor: 0x0,0x4
view constructor: 0x0,0x1987fe3ca
```
### 🔥C++において引数の評価順は規定されておらず、評価順によって結果が変わるコードの動作は未定義である
引数で実行する関数の依存関係がなく順不同で実行されても問題がないようにすること
### [syncstream - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/syncstream.html )
[[_TOC_]]
## コーディング時の注意事項
* [CERT C コーディングスタンダード 00\. はじめに]( https://www.jpcert.or.jp/sc-rules/00.introduction.html )
* [Google C\+\+ Style Guide]( https://google.github.io/styleguide/cppguide.html )
### 関数の出力
[Google C\+\+ Style Guide -Inputs_and_Outputs]( https://google.github.io/styleguide/cppguide.html#Inputs_and_Outputs )
* 基本的に戻り値を利用すること
* どうしても引数にする場合
* 入力引数の後に出力引数の順番とする
* 出力/入出力の変数はnon-const pointerを利用すること
e.g. `void Foo(const string &in, string *out);`
なお,入力としてポインタの情報が欲しい場合や`nullptr`である可能性がある場合には,`const T*`を利用しても良い(これは出力に`T&`ではなくポインタを利用するメリットの1つでもある)
[C\+\+ Core Guidelines: The Rules for in, out, in\-out, consume, and forward Function Parameter \- ModernesCpp\.com]( https://www.modernescpp.com/index.php/c-core-guidelines-how-to-pass-function-parameters )
では,`out`なケースは単独でも複数でも返り値として返す方法を採用し,`in-out`の場合はnon-const reference(`T&`)を採用している
* [void foo\(T& out\) \- How to fix output parameters]( https://foonathan.net/2016/10/output-parameter/ )
* [Input\-output arguments: reference, pointers or values? · Mathieu Ropert]( https://mropert.github.io/2018/04/03/output_arguments/ )
個人的には
* ポインタ利用: null pointerチェックがあるので,面倒であるし,バグの温床
* リファレンス利用: 呼び出し側で初期化忘れ防止の初期値が無駄になり,関数内でエラーでの早期returnのときに値を初期値にするのかという問題がでてくる
* 返り値で返す: 実はgoogle coding styleにも合致する方法で,良いとこどりなのではないか?
### 注意点が多く記述されているFAQ
[C\+\+ FAQ]( https://isocpp.org/wiki/faq )
### クラスのstaticフィールドの利用方法
[C\+\+の class template を使えば static メンバの実体がヘッダファイルに書けるカラクリ \- higepon blog]( https://higepon.hatenablog.com/entry/20100803/1280834422 )
#### 定数値をhppではなく、cppファイルに記述する方法
[explicit-define-static-data-mems \- Constructors, C\+\+ FAQ]( https://isocpp.org/wiki/faq/ctors#explicit-define-static-data-mems )
`std::string`はこちら
[c\+\+ \- Static constant string \(class member\) \- Stack Overflow]( https://stackoverflow.com/questions/1563897/static-constant-string-class-member )
#### 定数値をhpp、cppファイルに宣言を記述する方法
[static-const-with-initializers \- Constructors, C\+\+ FAQ]( https://isocpp.org/wiki/faq/ctors#static-const-with-initializers )
### `__`(ダブルアンダースコア)は変数のどこにあっても違反
[syntax \- Why do people use \_\_ \(double underscore\) so much in C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/224397/why-do-people-use-double-underscore-so-much-in-c )
## C++17
* [variant - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/variant/variant.html )
* [optional - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/optional/optional.html )
上記は、[sewenew/redis-plus-plus: Redis client written in C++]( https://github.com/sewenew/redis-plus-plus )にて活用されている
### 🔥directory_iterator(ファイルの走査順序は未規定)
* [directory_iterator - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/filesystem/directory_iterator.html )
* [std::directory_iterator に注意せよ]( https://zenn.dev/enchan1207/articles/29de772131de13 )
## 終了時のエラー回避のために
### 基底クラスが派生クラスのリソースへ依存している際の解放順番に注意
継承でスレッドを利用している例
``` cpp
#include <chrono>
#include <iostream>
#include <thread>
class Base {
public:
Base() { std::cout << "Base constructor" << std::endl; }
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
Join();
}
void Start() {
thread_ = std::thread([this]() { Callback(); });
}
void Join() {
if (thread_.joinable()) {
thread_.join();
}
}
virtual void Callback() = 0;
private:
std::thread thread_;
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor" << std::endl;
message_ = "alive";
}
~Derived() override {
std::cout << "Derived destructor" << std::endl;
// 継承元の明示的なJoin()が必要となる
Join();
message_ = "dead";
}
void Callback() override {
for (int i = 0; i < 10; i++) {
std::cout << "Callback:" << message_ << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
private:
std::string message_;
};
int main() {
Base* b = new Derived();
b->Start();
std::this_thread::sleep_for(std::chrono::milliseconds(300));
delete b;
}
```
``` bash
$ ./a.out
Base constructor
Derived constructor
Callback:alive
Callback:alive
Callback:alive
Derived destructor
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Base destructor
```
### フィールドメンバがスレッド経由でクラスのリソースへ依存している際の解放順番に注意
``` cpp
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>
class Worker {
public:
Worker() { std::cout << "Base constructor" << std::endl; }
virtual ~Worker() {
std::cout << "Worker destructor" << std::endl;
Join();
}
void Start(std::function<void(void)> callback) {
thread_ = std::thread([callback]() { callback(); });
}
void Join() {
if (thread_.joinable()) {
thread_.join();
}
}
private:
std::thread thread_;
};
class Hoge {
public:
Hoge() {
std::cout << "Hoge constructor" << std::endl;
message_ = "alive";
}
~Hoge() {
std::cout << "Hoge destructor" << std::endl;
// フィールド利用の明示的なJoin()が必要となる
worker_.Join();
message_ = "dead";
}
void Start() {
worker_.Start([this]() {
for (int i = 0; i < 10; i++) {
std::cout << "Callback:" << message_ << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
}
private:
Worker worker_;
std::string message_;
};
int main() {
Hoge hoge;
hoge.Start();
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
```
``` bash
$ ./a.out
Base constructor
Hoge constructor
Callback:alive
Callback:alive
Callback:alive
Hoge destructor
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Callback:alive
Worker destructor
```
## 継承
public, protected, privateの3種類の継承があるが,通常はpublic継承を利用するので,あまり気にする場面はないと思われる
[非public継承の使いどころ \| 闇夜のC\+\+]( http://cpp.aquariuscode.com/inheritance-use-case )
### 抽象クラスを利用するときには、ポインタとして利用する
抽象クラスはインスタンス化できない
### コンストラクタ/デストラクタの呼び出し順序
``` cpp
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base constructor" << std::endl; // 1
}
virtual ~Base() {
std::cout << "Base destructor" << std::endl; // 6
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor" << std::endl; // 2
}
~Derived() override {
std::cout << "Derived destructor" << std::endl; // 5
}
};
class DoubleDerived : public Derived {
public:
DoubleDerived() {
std::cout << "DoubleDerived constructor" << std::endl; // 3
}
~DoubleDerived() override {
std::cout << "DoubleDerived destructor" << std::endl; // 4
}
};
int main() {
Base* b = new DoubleDerived();
delete b;
}
```
``` bash
$ ./a.out
Base constructor
Derived constructor
DoubleDerived constructor
DoubleDerived destructor
Derived destructor
Base destructor
```
`Base``virtual`を外した場合の挙動
``` bash
$ ./a.out
Base constructor
Derived constructor
DoubleDerived constructor
Base destructor
```
### `virtual`は伝播するので、継承先で明記しなくてもよい
[virtual function specifier \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/virtual )
``` cpp
#include <iostream>
struct Base {
public:
virtual ~Base() { std::cout << "Base\n"; }
};
struct Derived : Base {
public:
/* virtual */ ~Derived() override { std::cout << "Derived\n"; }
};
struct DerivedParent : Derived {
public:
/* virtual */ ~DerivedParent() override { std::cout << "DerivedParent\n"; }
};
int main() {
Base* b = new DerivedParent();
delete b;
}
```
## enum
よく使う命名例
``` cpp
enum Error { kSuccess, kInvalidArgs };
```
## 構造体
### 構造体/クラスのフィールドのコロンで参照bit範囲を設定できる
[c\+\+ \- ":" \(colon\) in C struct \- what does it mean? \- Stack Overflow]( https://stackoverflow.com/questions/8564532/colon-in-c-struct-what-does-it-mean )
* そのフィールドにそれ以上の値を定数で代入すると、ビルド時に警告がある
* 超えた範囲は巡回する(参照bit範囲が固定なので、このように見えるだけでは?)
* 実行時にも有効で、`int v:2;`とすると、`-2,-1,0,1`の値のみを取る
## 関数
### `f(g(x), h(y))`の呼び出し順序は規定されていない
ちなみに、Rustは最近、left-to-rightであることが規定された
## ラムダ関数
* ラムダ関数のキャプチャについて`[&]`は利用しないこと
* 基本的には、`[hoge]`としてコピーするか、引数としする方法を取り、どうしても、参照のキャプチャをしたい場合には明示的に、変数名を指定して`[&hoge]`の形式を利用すること
* `[&this]`はできないので、`[this]`となる
### ローカル変数のキャプチャの遅延実行
* ローカルな変数をキャプチャして他のスレッドに渡して遅延実行はNG
* ダングリングポインタを参照することになり、直後に再帰関数などを呼ぶとスタックの内容が書き換わりやすい
* スレッドごとに、スタックが独立しているので、スレッドによって、破壊の傾向が変化することもある
* スタックの深い場所で発生すると他の箇所からの書き換わりにくいこともあり、発見しにくくなる
* 一見、`-O0`ビルドだとNGで`-O1,-O2,-O3`ビルドだとOKのように見えることがあるが、キャプチャ先のアドレスを参照するのでNG
* `-fstack-protector-all`では検出できない
* キャプチャした後にデータを書き込んでいると、`*** stack smashing detected ***`のような結果となる可能性が高い
* `-fsanitize=address`の場合、他でスタックが伸びて、初めてアクセスがあった場合に実行時に検出される
* 問題がある挙動になって初めて検出されるので、存在しないことの証明ができない
### パフォーマンス
[関数ポインタと関数オブジェクトのインライン展開 \- Qiita]( https://qiita.com/kaityo256/items/5911d50c274465e19cf6 )
関数オブジェクト、関数ポインタ、ラムダ関数の順に遅くなる(この場合、関数ポインタを利用しても速度が同じとなっている)
``` bash
$ g++ -std=c++11 function_object.cpp -O3
$ hyperfine -m 3 ./a.out
Benchmark #1: ./a.out
Time (mean ± σ): 183.4 ms ± 26.3 ms [User: 170.0 ms, System: 5.2 ms]
Range (min … max): 165.5 ms … 232.9 ms 12 runs
$ g++ -std=c++11 funcion_pointer.cpp -O3
$ hyperfine -m 3 ./a.out
Benchmark #1: ./a.out
Time (mean ± σ): 182.9 ms ± 32.0 ms [User: 171.3 ms, System: 5.5 ms]
Range (min … max): 165.5 ms … 260.3 ms 11 runs
$ g++ -std=c++11 funcional.cpp -O3
$ hyperfine -m 3 ./a.out
Benchmark #1: ./a.out
Time (mean ± σ): 1.012 s ± 0.024 s [User: 979.7 ms, System: 8.5 ms]
Range (min … max): 0.996 s … 1.039 s 3 runs
```
## 型
### unsigned char型またはunsigned short型などの演算結果はsigned int型となる
``` cpp
#include <cstdint>
#include <cstdio>
#include <typeinfo>
#define check_type(type) \
{ \
type x = 123; \
if (typeid(x).name() != typeid(~x).name()) { \
printf("%20s type is %s\n", #type, typeid(x).name()); \
printf("%20s type is %s\n", "~" #type, typeid(~x).name()); \
if (typeid(~x).name() != typeid(x << 1).name()) { \
printf("%20s type is %s\n", #type " << 1", typeid(x << 1).name()); \
} \
} else { \
printf("%20s type is %s\n", #type, "same"); \
} \
printf("\n"); \
}
int main(int argc, char* argv[]) {
check_type(unsigned char);
check_type(uint8_t);
check_type(signed char);
check_type(char);
check_type(int8_t);
check_type(unsigned short);
check_type(uint16_t);
check_type(short);
check_type(int16_t);
check_type(uint32_t);
check_type(int32_t);
check_type(unsigned int);
check_type(int);
check_type(uint64_t);
check_type(int64_t);
check_type(bool);
// check_type(float);
// check_type(double);
return 0;
}
```
```
unsigned char type is h
~unsigned char type is i
uint8_t type is h
~uint8_t type is i
signed char type is a
~signed char type is i
char type is c
~char type is i
int8_t type is a
~int8_t type is i
unsigned short type is t
~unsigned short type is i
uint16_t type is t
~uint16_t type is i
short type is s
~short type is i
int16_t type is s
~int16_t type is i
uint32_t type is same
int32_t type is same
unsigned int type is same
int type is same
uint64_t type is same
int64_t type is same
bool type is b
~bool type is i
```
## template
### インスタンス化
例えば、テンプレートを明示的にインスタンス化して実装をcpp側にすることで、通常の関数のように扱うテクニックがある
* [テンプレートのインスタンス化 | Programming Place Plus C++編【言語解説】 第21章]( https://programming-place.net/ppp/contents/cpp/language/021.html )
* [[C++]特殊化?実体化??インスタンス化???明示的????部分的????? - 地面を見下ろす少年の足蹴にされる私]( https://onihusube.hatenablog.com/entry/2020/01/24/183247 )
### 関数の戻り値のテンプレート
ユーザに戻り値を決定させたい場合に、明示的に指定する箇所を最低限にしたい場合は最初に返り値の型を指定させるようにするとよい
``` cpp
template <typename RET_TYPE, typename T1, typename T2>
RET_TYPE sum(T1 a, T2 b) {
return a + b;
}
sum<int>(1, 2);
```
### 後置の戻り値の型と型変換
パラメータが検出されるまで,変数は存在しないため,戻り値の型を後置にしなければならない
``` cpp
template<typename I>
auto func(I beg, I end) -> decltype(*beg) {
// ...
return *beg;
}
```
## 参照
### `&`付きの型に対して,自由に値を代入したい
* [C\+\+ \- 参照の初期化を条件分岐で行う方法について|teratail]( https://teratail.com/questions/158884 )
* [std::vector で参照を保持したい \- Secret Garden\(Instrumental\)]( http://secret-garden.hatenablog.com/entry/2015/08/28/000000 )
``` cpp
#include <functional>
#include <iostream>
#include <string>
#include <vector>
void print_first_one(const std::vector<std::string>& vec) {
std::cout << vec[0] << std::endl;
}
int main(int argc, char* argv[]) {
std::vector<std::string> a = {"alice"};
std::vector<std::string> b = {"bob"};
std::reference_wrapper<std::vector<std::string>> target_ref = a;
std::cout << target_ref.get()[0] << std::endl;
print_first_one(target_ref);
target_ref = b;
std::cout << target_ref.get()[0] << std::endl;
print_first_one(target_ref);
return 0;
}
```
## new
### placement new
[『placement new』自分でメモリを管理してみしょう \| GAMEWORKS LAB]( http://gameworkslab.jp/2020/01/28/%E3%80%8Eplacement-new%E3%80%8F%E8%87%AA%E5%88%86%E3%81%A7%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%92%E7%AE%A1%E7%90%86%E3%81%97%E3%81%A6%E3%81%BF%E3%81%97%E3%82%87%E3%81%86/ )
#### コンストラクタは自動的に呼ばれる
#### デストラクタは明示的に呼び出す必要がある(deleteを呼んではならない)
[c\+\+ \- why destructor is not called implicitly in placement new"? \- Stack Overflow]( https://stackoverflow.com/questions/1022320/why-destructor-is-not-called-implicitly-in-placement-new )
## main関数
[implementation-defined]( https://en.cppreference.com/w/cpp/language/main_function )
> A very common implementation-defined form of main() has a third argument (in addition to argc and argv), of type char*[], pointing at an array of pointers to the execution environment variables.
`envp`は処理系定義
``` cpp
int main(int argc, char *argv[], char *envp[]) { }
```
FYI: [\`main\` function and command-line arguments (C++) | Microsoft Docs]( https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#the-envp-command-line-argument )
## デバッグ
### `__PRETTY_FUNCTION__`
[事前定義識別子\_\_func\_\_ \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/lang/cpp11/func.html )
GCCの言語拡張で、名前空間名、クラス名、戻り値やパラメータといった情報も含む関数の文字列となる
## std::offstream
### error handling
* [c\+\+ \- Error handling in std::ofstream while writing data \- Stack Overflow]( https://stackoverflow.com/questions/28342660/error-handling-in-stdofstream-while-writing-data )
* [c\+\+ \- Get std::fstream failure error messages and/or exceptions \- Stack Overflow]( https://stackoverflow.com/questions/839644/get-stdfstream-failure-error-messages-and-or-exceptions )
__`write`したあとに`flush`して、初めて書き込みが実際に行われて、エラーがどうかがわかる__
例えば、`cgroup``cpu`への`tasks`ファイルへ書き込む際には、書き込むPID,TIDの設定(e.g. スケジューリング)に依存して、エラーとなる可能性があり、書き込む内容に依存した結果となる
## `std::vector`
### 非ゼロ値で初期化したい
``` cpp
std::vector<int> vec = {1, 2, 3};
vec.resize(5, 10);
```
`{1, 2, 3, 10, 10}`
### 確保したバッファを削除したい
`clear()``resize(0)`もキャパシティはそのまま確保された状態になるので、`shrink_to_fit()`で切り詰めないと意図した動作にはならない
### std::remove_if
[remove\_if \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/algorithm/remove_if.html )
削除する要素を詰めて、サイズが縮んだコンテナの新しい終端イテレータを返す仕様であり、要素は削除されないので、サイズは変わらない
### コピーしたい場合
[std vector C\+\+ \-\- deep or shallow copy \- Stack Overflow]( https://stackoverflow.com/questions/11348376/std-vector-c-deep-or-shallow-copy )
* `=`: 各要素の値をdeep copyする
* もし、要素自身がポインタならば意味的にはshallow copyとなっている
* 型が異なる要素のコピーを行う場合は、`std::copy`を利用する
``` cpp
std::vector<uint32_t> vec;// dst
std::vector<int> vec_tmp;// src
vec.resize(vec_tmp.size());
std::copy(std::begin(vec_tmp), std::end(vec_tmp), std::begin(vec));
```
### `std::vector`を関数の引数にそのまま指定できるかどうか
``` cpp
#include <vector>
void f_vec_ref(std::vector<int>& vec) {}
void f_vec(std::vector<int> vec) {}
int main(int argc, char* argv[]) {
std::vector<int> vec;
vec = {0, 1, 2, 3, 4};
f_vec(vec);
f_vec({0, 1, 2, 3, 4});
f_vec_ref(vec);
// NG
// f_vec_ref({0, 1, 2, 3, 4});
return 0;
}
```
### `std::vector<bool>`
[On vector\<bool> \-\- Howard Hinnant : Standard C\+\+]( https://isocpp.org/blog/2012/11/on-vectorbool )
* __自動でビットを利用するように最適化される__(内部的にはアロケータとして`std::allocator<bool>`が利用されている)
* 要素のポインターを取得できない
* `.data()`メソッドが実装されていない
``` cpp
std::vector<bool> v(8);
bool* p = &v[0];
```
```
error: no viable conversion from '__bit_iterator<std::__1::vector<bool,
std::__1::allocator<bool> >, false>' to 'bool *'
```
結局の所,イテレータでのアクセスを前提として利用する形式なので,`int64_t`などの型を利用して効率的に演算は正攻法ではできない
サイズ固定ではない`std::bitset`が欲しい場合には`boost::dynamic_bitset`を利用することになりそう
`boost::dynamic_bitset<uint8_t> bit_data;`として,`bit_data.append(0xFF)`とすれば,規定サイズ単位の数値は簡単に追加できる(上位bit方向(先頭)に追加されることに注意)
逆に,`10bit`単位の数を上記のように作成することはできなさそう
### [std::vector をがんばって高速に resize する \- Qiita]( https://qiita.com/i_saint/items/59c394a28a5244ec94e1 )
## std::regex
### 注意点
``` cpp
if (std::regex_match(std::string("str[i]"), match, re)) {}
```
第1引数はmatchで値を取り出すまで,スコープが有効でなければならないので,上記の例はNG
[regex\_match \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/regex/regex_match.html )
> match_results オブジェクトを引数に取る形式の場合、そのオブジェクトは引数で指定した検索対象文字列へのイテレータを保持する。
このため、検索対象文字列は本関数を呼び出した後も match_results オブジェクトを使用し終わるまで破棄されないようにする必要がある。
## std::shared_ptr
### std::shared_ptrでラップしても、基底クラスに継承クラスを代入可能である
### コピー代入ではなくstd::moveを利用するとパフォーマンスが改善できる
[shared_ptr - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/memory/shared_ptr.html )
> 非スレッドセーフに参照カウントを増減させる方法はない。シングルスレッドでのパフォーマンスが重要で、スレッドセーフであることによるオーバーヘッドが問題になる場合、ムーブを活用すればパフォーマンスを改善できる。
``` diff
- piyo = hoge_shared_ptr;
+ piyo = std::move(hoge_shared_ptr);
```
### ラムダ関数との組み合わせの注意点
[cpp\-examples/pitfalls/shared\_ptr at master · umaumax/cpp\-examples]( https://github.com/umaumax/cpp-examples/tree/master/pitfalls/shared_ptr )
[enable_shared_from_this - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/memory/enable_shared_from_this.html )に関連するネタ?
## `std::execution`
[実行ポリシー \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/execution/execution/execution_policy.html )
`C++17`から有効な機能
例えば、`std::sort`などを並列で実行することができる
## `std::this_thread::yield`
ビジーウェイトとする場合にはこれを呼び出して明示的にCPUを明け渡すと行儀が良い
* [yield \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/thread/this_thread/yield.html )
疑問点: 通常の`sleep()`[std::this\_thread::yield \- cppreference\.com]( https://en.cppreference.com/w/cpp/thread/yield )の例のようなsleepのどちらがよい?(ケース・バイ・ケースだと思われるが,短い時間のsleepならば後者こちらの方が明示的にCPUを明け渡せるのでよいのかも)
## `std::cout`
### `std::cout`を`std::ostream`として扱いたい
[c\+\+11 \- std::ostream to file or standard output \- Stack Overflow]( https://stackoverflow.com/questions/23345504/stdostream-to-file-or-standard-output )
``` cpp
std::ostream(std::cout.rdbuf())
```
### [std::cout 乗っ取り計画 \- Qiita]( https://qiita.com/yohhoy/items/1dce1c0d19baae48ae78 )
`std::cout.rdbuf()`を取得して、カスタムしたものを再度、設定する手順を踏む
## std::chrono
### std::chrono::duration
`std::chrono::milliseconds(duration)``duration`の単位に`double`を利用するとわかりにくいビルドエラーとなることに注意
### std::chrono::system_clock::time_point
``` cpp
#include <chrono>
#include <iostream>
int main(int argc, char* argv[]) {
std::chrono::system_clock::time_point zero{};
std::chrono::system_clock::time_point min =
std::chrono::system_clock::time_point::min();
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
double sub =
std::chrono::duration_cast<std::chrono::milliseconds>(now - zero).count();
std::cout << "zero:" << zero.time_since_epoch().count() << std::endl;
std::cout << "min:" << min.time_since_epoch().count() << std::endl;
std::cout << "now:" << now.time_since_epoch().count() << std::endl;
std::cout << "sub:" << sub << std::endl;
return 0;
}
// zero:0
// min:-9223372036854775808
// now:1589511568463908655
// sub:1.58951e+12
```
### `std::chrono::system_clock::now()`
実装で最終的に呼び出される`clock_gettime`は発行CPUに関わらず、共通の結果となるように見える
[c\+\+ \- Where does the time from \`std::chrono::system\_clock::now\(\)\.time\_since\_epoch\(\)\` come from and can it block if accessed from multiple threads? \- Stack Overflow]( https://stackoverflow.com/questions/46740302/where-does-the-time-from-stdchronosystem-clocknow-time-since-epoch-c/46740824#46740824 )
> A typical C++ standard library implementation would rely on the underlying OS system call to get the actual system clock value to construct the time_point object.
[gcc/chrono\.cc at master · gcc\-mirror/gcc]( https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B11/chrono.cc#L80 )
> syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &tp);
> clock_gettime(CLOCK_MONOTONIC, &tp);
[linuxで時刻を取得する方法 \- pyopyopyo \- Linuxとかプログラミングの覚え書き \-]( https://pyopyopyo.hatenablog.com/entry/20060711/p1 )
各アーキテクチャでの実装例はGolangの実装を見ると良さそう
[Goとrdtscの謎を追う \- Qiita]( https://qiita.com/kubo39/items/4319fa243fd18acc0981 )
[Man page of CLOCK\_GETRES]( https://linuxjm.osdn.jp/html/LDP_man-pages/man2/clock_getres.2.html )
> CLOCK_PROCESS_CPUTIME_ID (Linux 2.6.12 以降)
> プロセス単位の CPU タイムクロック (そのプロセスの全スレッドで消費される CPU 時間を計測する)。
> CLOCK_THREAD_CPUTIME_ID (Linux 2.6.12 以降)
> スレッド固有の CPU タイムクロック。
`CLOCK_MONOTONIC`では、基本的にCPU間の問題は回避できているように見える
## std::map, std::unordered_map
### `[]`アクセス
存在する場合はその値を、存在しない場合はデフォルト値(デフォルトコンストラクタが存在する場合)を返すことに注意
### `insert()`
存在しない場合は追加、存在する場合は無視という挙動となることに注意
### [mapのキーにvectorが使える \- minus9d's diary]( https://minus9d.hatenablog.com/entry/20120610/1339332308 )
`比較演算子`がある必要がある
### [俺のunordered\_mapがこんなにpair型をキーにできないわけがない \- Qiita]( https://qiita.com/ganyariya/items/df35d253726269bda436 )
`ハッシュ可能`である必要がある(第3項目に自作ハッシュ関数を定義すれば良い)
## 文字列からの数値変換 - `std::stol`
* c++
* [stol - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/string/stol.html )
* 例外を投げる
* > パラメータ idx が非 nullptr の場合、変換に使用されなかった要素のインデックス( end - str.c_str() )が格納される。
* 先頭が数字であれば例外は投げないため、この引数を指定することで、与えた文字列がすべて適切に利用されたかどうかを判定できる
* c
* [Man page of STRTOL]( https://linuxjm.osdn.jp/html/LDP_man-pages/man3/strtol.3.html )
* 例外を投げない
## `std::function`
[std::function のコスト \- Kludge Factory]( https://tyfkda.github.io/blog/2020/03/04/std-function-runtime.html )
### `std::function::target()`
[本の虫: std::functionのtargetの使い方]( https://cpplover.blogspot.com/2010/11/stdfunctiontarget.html )
> std::function経由で呼び出すには、格納されている型を実行時に判断しなければならない。そのため、すでに格納されている型が分かっているのならば、中身を取り出すことで、そのような実行時チェックを、中身を取り出す際の一回で済ませることができる。
``` cpp
auto func = *f.target< int (*)(int) >();
for (int i = 0 ; i != 1000000; ++i) {
func(i);
}
```
[関数ポインタ型を混在させて運用する \- C\+\+ プログラミング]( https://ez-net.jp/article/6A/su9HMTVF/wKanc4EjbZFS/ )
ラムダ関数を`target()`で取り出すうまい方法がない
#### `std::function`の中身が指している関数の同一性の比較
直接、`==`を利用して比較してはならず、`target()`を経由する
基本的に`target()`にて、 __正しい型__ を指定できていることが前提なので,下記のような比較ではなく,どの関数を代入しているかどうかのメタ情報を管理するほうがメンテナンス性がよいと考えられる(C関数ポインタであるか,ラムダ関数であるか,`std::bind`を利用しているかなどによって,この型が変わってしまう)
``` cpp
#include <cassert>
#include <functional>
#include <iostream>
void f() { std::cout << "f" << std::endl; }
void g() { std::cout << "g" << std::endl; }
int main(int argc, char const* argv[]) {
std::function<void(void)> func_f1(f); // or &f
std::function<void(void)> func_f2(f);
std::function<void(void)> func_g(g);
std::function<void(void)> func_lambda(
[]() { std::cout << "lambda" << std::endl; });
// NOTE: target() return the pointer to function pointer
// WARN: if type is not correct, target() return nullptr
assert(func_f1.target<void(void)>() == nullptr);
assert(func_lambda.target<void(void)>() == nullptr);
assert(func_lambda.target<void (*)(void)>() == nullptr);
assert(func_f1.target<void (*)(void)>() != nullptr);
assert(func_f2.target<void (*)(void)>() != nullptr);
assert(func_g.target<void (*)(void)>() != nullptr);
auto func_f1_p = *func_f1.target<void (*)(void)>();
auto func_f2_p = *func_f2.target<void (*)(void)>();
auto func_g_p = *func_g.target<void (*)(void)>();
printf("f:%p\n", &f);
printf("g:%p\n", &g);
printf("func_f1:%p\n", func_f1_p);
printf("func_f2:%p\n", func_f2_p);
printf("func_g:%p\n", func_g_p);
assert(func_f1_p == func_f2_p);
assert(func_f1_p == &f);
assert(func_g_p == &g);
func_f1();
func_f2();
func_g();
func_lambda();
return 0;
}
```
## algorithm header
[mismatch - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/algorithm/mismatch.html )
## glibc
### glibcのソースコードの検索ページ
[bminor/glibc: Unofficial mirror of sourceware glibc repository\. Updated daily\.]( https://github.com/bminor/glibc )
## ヘッダ
### unistd.h
UNIx STanDard Header file
### ヘッダファイルはヘッダとソースのどちらでincludeするべき?
* [Google C\+\+ Style Guide - Include_What_You_Use]( https://google.github.io/styleguide/cppguide.html#Include_What_You_Use )
* [c\+\+ \- Including \#includes in header file vs source file \- Stack Overflow]( https://stackoverflow.com/questions/2596449/including-includes-in-header-file-vs-source-file/2596554 )
必要なファイルでincludeするべきである
理由の一つとしては、そのヘッダをincludeしたときに実装のみで必要となるヘッダも余計にincludeしてしまい、無駄であるから
### 余計なヘッダファイルの見つけ方
余計と思われるヘッダを減らした際に`-c`オプション付きでビルドできるかどうかを基準とする方法がある
* [include\-what\-you\-use/include\-what\-you\-use: A tool for use with clang to analyze \#includes in C and C\+\+ source files]( https://github.com/include-what-you-use/include-what-you-use )
* [include\-what\-you\-useとjenkinsでC/C\+\+プロジェクトから不要な\#includeを洗い出す \- Qiita]( https://qiita.com/tomota-tar-gz/items/985b660e8f3052a387ef )
## 初期化
* [C言語 未初期化変数の罠 \- 気まま研究所ブログ]( https://aonasuzutsuki.hatenablog.jp/entry/2018/12/21/111450 )
* [変数の初期化をサボるな,それから \-Wshadow オプションを使え \- akihiko’s tech note]( https://aki-yam.hatenablog.com/entry/20130718/1374163303 )
### 複雑な処理をした結果のconst初期化
[C++ Core Guidelines]( https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es28-use-lambdas-for-complex-initialization-especially-of-const-variables )
e.g.
``` cpp
const widget x = [&] {
widget val; // assume that widget has a default constructor
for (auto i = 2; i <= N; ++i) { // this could be some
val += some_obj.do_something_with(i); // arbitrarily long code
} // needed to initialize x
return val;
}();
```
### 配列の初期化
``` cpp
#include <array>
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
std::array<int, 800> q; // NG
// std::array<int, 800> q = {}; // OK
for (int i = 0; i < 800; i++) {
if (q[i] != 0) {
std::cout << q[i] << std::endl;
}
}
// initialize by fill
std::fill(q.begin(), q.end(), 0);
return 0;
}
```
デフォルトで不定値
``` bash
1920169263
1651076143
1819894831
100
32
2
4
4
2
```
[Array initialization \- cppreference\.com]( https://en.cppreference.com/w/c/language/array_initialization )
> In C, the braced list of an initializer cannot be empty. C++ allows empty list:
> int a[3] = {0}; // valid C and C++ way to zero-out a block-scope array
> int a[3] = {}; // invalid C but valid C++ way to zero-out a block-scope array
Macで試すとCでも問題なくビルドできた
### structの初期化方法
[C\+\+における構造体の初期化 \| 株式会社きじねこ]( http://www.kijineko.co.jp/node/681 )
`Hoge hoge = {};`でOK
``` cpp
struct A
{
int a;
int b;
};
A a = { };
```
> のようにすれば、すべてのデータメンバをゼロ初期化または省略時初期化(データメンバが利用者定義のコ<ンストラクタを持つクラスの場合)することができます。
> Cの場合は、{ } の中に少なくともひとつの初期値を記述しなければなりませんが、C++ではまったく書かなくてもOKです。
> { } による初期化にせよ、( ) による初期化にせよ、処理系によっては内部的にmemsetを呼び出していることも少なからずあります。
この場合のように,`memset()`では`double``float`など危険である
### memsetで0埋めならばたまたま想定通りになる
[浮動小数点数の内部表現\(IEEE\)]( https://www.k-cube.co.jp/wakaba/server/floating_point.html )
``` cpp
数値:0.0
d = 0.000000
00 00 00 00 00 00 00 00
f = 0.000000
00 00 00 00
```
IEEE 754の規格という前提
### newで初期化子が利用できる
[初期化子リスト \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/lang/cpp11/initializer_lists.html )
e.g.
``` cpp
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
double* buf = new double[9]{};
for (int i = 0; i < 9; i++) {
std::cout << buf[i] << std::endl;
}
return 0;
}
```
### 文字列配列の初期化
[STR11\-C\. 文字列リテラルで初期化される文字配列のサイズを指定しない]( https://www.jpcert.or.jp/sc-rules/c-str11-c.html )
``` cpp
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
const char* name_list[] = {"ab", "cd", "ef", "gh", "ij"};
// or
// const char name_list[5][3+1]
// const char* name_list[5]
std::cout << sizeof("ab") << std::endl; // 2+1
std::cout << sizeof(char*) << std::endl; // 8(x86_64)
std::cout << sizeof(name_list) << std::endl; // 5*sizeof(char*)=5*8
std::cout << sizeof(name_list[0]) << std::endl; // sizeof(char*)=8
for (const char* name : name_list) {
std::cout << name << std::endl;
}
// or use std::string without const
// std::string name_list[] = {"ab", "cd", "ef", "gh", "ij"};
return 0;
}
```
注意
* [Size of character \('a'\) in C/C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/2172943/size-of-character-a-in-c-c )
* [sizeof演算子にまつわるアレコレ \- Qiita]( https://qiita.com/yohhoy/items/a2ab2900a2bd36c31879 )
``` cpp
const char* ok_list[] = {"ab", "cd", "ef", "gh", "ij"};
class Hoge {
const char* ok_list[5] = {"ab", "cd", "ef", "gh", "ij"};
// error: initializer for flexible array member ‘const char* Hoge::ng_list []’
const char* ng_list[] = {"ab", "cd", "ef", "gh", "ij"};
};
```
* [c \- How to initialize a structure with flexible array member \- Stack Overflow]( https://stackoverflow.com/questions/8687671/how-to-initialize-a-structure-with-flexible-array-member )
* [DCL38\-C\. フレキシブル配列メンバには正しい構文を使用する]( https://www.jpcert.or.jp/sc-rules/c-dcl38-c.html )
* [フレキシブル配列メンバをC\+\+で \- 茂加部珈琲店]( http://mocabe.hatenablog.com/entry/2018/05/23/121706 )
* [C言語のフレキシブル配列メンバ(flexible array member)、通称struct hack \| NO MORE\! 車輪の再発明]( https://mem-archive.com/2018/08/10/post-529/ )
* あくまでCの構文?
### `std::atomic`の初期値
* [c\+\+ \- What's the default value for a std::atomic? \- Stack Overflow]( https://stackoverflow.com/questions/36320008/whats-the-default-value-for-a-stdatomic )
* [atomic::コンストラクタ \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/reference/atomic/atomic/op_constructor.html )
* C++17までは不定値
* `{}`で初期化可能
* C++20からは値ゼロで初期化
### グローバル変数/スレッドローカル変数(ゼロ初期化)
[Zero initialization \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/zero_initialization )
> For every named variable with static or thread-local storage duration that is not subject to constant initialization, before any other initialization.
下記のように、明示的に処理化されていないグローバル変数およびスレッドローカル変数はゼロ初期化されるが、明示的に初期化子を記述したほうがわかりやすい
``` cpp
// global variables
double f[3]; // zero-initialized to three 0.0's
int* p; // zero-initialized to null pointer value
std::string s; // zero-initialized to indeterminate value then default-initialized to ""
```
### ローカル変数/クラスメンバー(ゼロ初期化)
[Value initialization \(since C\+\+03\) \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/value_initialization )
``` cpp
int n{}; // scalar => zero-initialization, the value is 0
double f = double(); // scalar => zero-initialization, the value is 0.0
int* a = new int[10](); // array => value-initialization of each element the value of each element is 0
```
デフォルトコンストラクタが実装されていない構造体の場合(明示的にコンストラクタを実装した場合には暗黙的なデフォルトコンストラクタが実装されなくなる)は`{}``()`が利用できなくなる
実質、`()`(C++03)と`{}`(C++11)の初期化に違いはないはず...
`float* ary = new float[1000]();``float* ary = new float[1000]{};`のアセンブリが一致した
FYI: [Aggregate initialization \- cppreference\.com]( https://en.cppreference.com/w/cpp/language/aggregate_initialization )
[Array initialization \- cppreference\.com]( https://en.cppreference.com/w/c/language/array_initialization )
``` cpp
int a[3] = {0}; // valid C and C++ way to zero-out a block-scope array
int a[3] = {}; // invalid C but valid C++ way to zero-out a block-scope array
```
### static変数の初期化はスレッドセーフで行われる
[ブロックスコープを持つstatic変数初期化のスレッドセーフ化 \- cpprefjp C\+\+日本語リファレンス]( https://cpprefjp.github.io/lang/cpp11/static_initialization_thread_safely.html )
> ブロックスコープを持つstatic変数の初期化は、スレッドセーフであることが規定された。
> static変数の初期化が完了するまで、他のスレッドは初期化処理の前で待機する。
### グローバル変数の初期化順
[How do I prevent the “static initialization order problem”?]( https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use )
* 仕様で順番は規定されていない
* 複数ファイルにまたがる場合に、順番を明確にしたい場合は関数経由の呼び出しかgccの拡張機能(`__attribute__((init_priority(101)))`)を利用する
* Mac OS Xのgccでは利用できない
[\_\_attribute\_\_\)\(\(init\_priority\(N\)\)\)\(で、C\+\+のグローバル変数の初期化順序を制御する \- memologue]( https://yupo5656.hatenadiary.org/entry/20070203/p1 )
> C++の規格では、コンパイル単位をまたいだグローバル変数の初期化順序は規定されていませんので、g++に責任はありません。
> この初期化順が不定な問題は、init_priority属性を使うと手軽に解決できます。
> init_priority(N)のNの部分の数字が「優先度」で、101以上、65535以下の数値を書けます。小さい数字を指定した変数が先に初期化されるようになります。
> リンカスクリプトを見る限り、「同じ優先度の変数同士の初期化順は、リンクしてみないとわからない」ことと、スクリプトに KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) が先に書かれていることから、「init_priority指定のない変数と、init_priority(65535) の変数では、後者の初期化の方が先に行われる」こともわかります。
> init_priorityの番号による初期化順の制御は、単一の.so内、および単一の実行ファイル内でのみ効くようですね。
グローバス変数の次に、スレッドローカルな変数が初期化されることを挙動から確認
``` cpp
// 1
Hoge hoge;
// 2
thread_local __attribute__((init_priority(101))) Fuga fuga;
```
#### グローバル変数の初期化順(実験による観測)
どのスコープでも、同一のスコープならば、上の行が優先される
* ファイルスコープ
* `main()`前に初期化される
* `__attribute__((init_priority(101)))`の値が小さい方
* ファイルスコープTLS
* 呼ばれたタイミング(ただし、1つでもファイルスコープTLSが呼ばれたタイミングで、ファイルスコープTLS全体の初期化処理が行われる模様)
* どのファイルスコープTLSが呼ばれたかどうかは関係ない
* 明示的に呼び出していない場合には、一切呼び出されないことに注意
* 関数スコープ
* 呼ばれたタイミング
* 関数スコープTLS
* 呼ばれたタイミング
* 関数スコープの値をどうしても早く初期化したい場合は、ファイルスコープからその関数を呼び出せば良い
* 可能な限り、早く値を初期化したい場合には`__attribute__((init_priority(101)))`を付与したファイルスコープの初期化時に、初期化したい対象を利用すればよい
* `thread_local``__attribute__((init_priority(101)))`を設定可能だが、意味がない
* `__attribute__((init_priority(101)))`は、クラスに対してかつファイルスコープの変数のみに利用可能
なお、macの`g++-11`では、`init_priority`が有効ではなかった
``` cpp
#include <iostream>
class Hoge {
public:
Hoge(int input) { v = input; }
int v;
};
int f(int input);
int v = 0;
// thread_local __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 1
// thread_local __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// thread_local __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 3
// thread_local __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 1
// thread_local __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// thread_local __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 3
// __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 1
// __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 3
// __attribute__((init_priority(103))) Hoge outer_hoge3(++v); // 1
// __attribute__((init_priority(102))) Hoge outer_hoge2(++v); // 2
// __attribute__((init_priority(101))) Hoge outer_hoge1(++v); // 3
// Hoge outer_hoge3(++v); // 1
// Hoge outer_hoge2(++v); // 2
// Hoge outer_hoge1(++v); // 3
Hoge outer_hoge1(++v); // 1
Hoge outer_hoge2(++v); // 2
Hoge outer_hoge3(++v); // 3
int f(int input) {
// can only use ‘init_priority’ attribute on file-scope definitions of objects of class type
//
// static thread_local Hoge inner_hoge3(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge1(++v); // 3
// static thread_local Hoge inner_hoge1(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge3(++v); // 3
// static Hoge inner_hoge3(++v); // 1
// static Hoge inner_hoge2(++v); // 2
// static Hoge inner_hoge1(++v); // 3
static Hoge inner_hoge1(++v); // 1
static Hoge inner_hoge2(++v); // 2
static Hoge inner_hoge3(++v); // 3
// Hoge inner_hoge1(++v); // 1
// Hoge inner_hoge2(++v); // 2
// Hoge inner_hoge3(++v); // 3
std::cout << "f() outer hoge1:" << outer_hoge1.v << std::endl;
std::cout << "f() outer hoge2:" << outer_hoge2.v << std::endl;
std::cout << "f() outer hoge3:" << outer_hoge3.v << std::endl;
std::cout << "f() inner hoge1:" << inner_hoge1.v << std::endl;
std::cout << "f() inner hoge2:" << inner_hoge2.v << std::endl;
std::cout << "f() inner hoge3:" << inner_hoge3.v << std::endl;
return input;
}
int main(int argc, char* argv[]) {
// can only use ‘init_priority’ attribute on file-scope definitions of objects of class type
// static thread_local Hoge inner_hoge3(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge1(++v); // 3
// static thread_local Hoge inner_hoge1(++v); // 1
// static thread_local Hoge inner_hoge2(++v); // 2
// static thread_local Hoge inner_hoge3(++v); // 3
// static Hoge inner_hoge3(++v); // 1
// static Hoge inner_hoge2(++v); // 2
// static Hoge inner_hoge1(++v); // 3
static Hoge inner_hoge1(++v); // 1
static Hoge inner_hoge2(++v); // 2
static Hoge inner_hoge3(++v); // 3
// Hoge inner_hoge1(++v); // 1
// Hoge inner_hoge2(++v); // 2
// Hoge inner_hoge3(++v); // 3
std::cout << "outer hoge1:" << outer_hoge1.v << std::endl;
std::cout << "outer hoge2:" << outer_hoge2.v << std::endl;
std::cout << "outer hoge3:" << outer_hoge3.v << std::endl;
std::cout << "inner hoge1:" << inner_hoge1.v << std::endl;
std::cout << "inner hoge2:" << inner_hoge2.v << std::endl;
std::cout << "inner hoge3:" << inner_hoge3.v << std::endl;
return 0;
}
```
#### 確実にグローバル変数の値を取得するためには、関数スコープのグローバル変数を利用すると良い
``` cpp
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include <string>
pid_t get_cached_pid() {
static pid_t pid = []() { return getpid(); }();
return pid;
}
void print_pid(std::string message);
void print_cached_pid(std::string message);
struct PrePID {
PrePID() {
print_pid("pre pid global variable constructor");
print_cached_pid("pre pid global variable constructor");
}
} pre_pid;
pid_t pid = getpid();
struct PostPID {
PostPID() {
print_pid("post pid global variable constructor");
print_cached_pid("pre pid global variable constructor");
}
} post_pid;
void print_pid(std::string message) {
printf("pid at %s:%d\n", message.c_str(), pid);
}
void print_cached_pid(std::string message) {
printf("cached pid at %s:%d\n", message.c_str(), get_cached_pid());
}
int main(int argc, char* argv[]) {
print_pid("main func");
return 0;
}
```
``` bash
pid at pre pid global variable constructor:0
cached pid at pre pid global variable constructor:96839
pid at post pid global variable constructor:96839
cached pid at pre pid global variable constructor:96839
pid at main func:96839
```
## `nullptr`のクラスオブジェクトでメソッドを呼び出してみる
:warning: クラスオブジェクトのポインタを参照する処理(e.g. フィールドを参照する)などが内部で行わなければ問題なく実行できてしまう
``` cpp
#include <iostream>
class Hoge {
public:
void PrintArgValue(int x) { std::cout << "arg x=" << x << std::endl; }
void PrintFieldValue() { std::cout << "field x=" << x_ << std::endl; }
private:
int x_ = 123;
};
int main(int argc, char* argv[]) {
Hoge hoge;
hoge.PrintArgValue(456);
hoge.PrintFieldValue();
Hoge* hoge_nunllptr = nullptr;
hoge_nunllptr->PrintArgValue(789);
(reinterpret_cast<Hoge*>(0))->PrintArgValue(789);
// WARN: segmentation fault
hoge_nunllptr->PrintFieldValue();
return 0;
}
```
## 早見表
* [IEEE 754 演算のルール \- C\+\+ の歩き方 \| CppMap]( https://cppmap.github.io/articles/ieee754-arithmetic/ )
* [C\+\+ 型特性 早見表 \- C\+\+ の歩き方 \| CppMap]( https://cppmap.github.io/articles/type-traits/ )
## ADL(Argument Dependent Lookup: 実引数依存の名前探索)
* [C\+\+ ADLとは|問題と危険性、回避方法(明示的回避と事前回避) \| MaryCore]( https://marycore.jp/prog/cpp/about-adl-avoid-adl/ )
* [Argument Dependent Lookup \| 闇夜のC\+\+]( http://cpp.aquariuscode.com/argument-dependent-lookup )
## unnamed namespace in header file
* `.cpp`でnamespaceを定義する場合は1つであるが、ヘッダファイルの場合は複数の翻訳単位で存在することになることに注意(headerで読み込んだファイルは同一ファイル内であるとみなされるため)
* 慣例として、`detail`を利用する(`impl`, `internal`を利用する例もある)
* `detail`: [tinyformat/tinyformat\.h at master · c42f/tinyformat]( https://github.com/c42f/tinyformat/blob/master/tinyformat.h#L181 )
* `detail`, `impl`: boost header
* 標準ライブラリは`std::__detail::`を利用している
## 文字列
### `std::string(nullptr)`は違反
[c\+\+ \- Assign a nullptr to a std::string is safe? \- Stack Overflow]( https://stackoverflow.com/questions/10771864/assign-a-nullptr-to-a-stdstring-is-safe )
### null文字の扱い
* [serial port \- does read\(\) in c read null character \- Stack Overflow]( https://stackoverflow.com/questions/50625105/does-read-in-c-read-null-character )
* `read()``\0`関係なく一定バイト数読む
* [std::string の途中に null 文字が含まれている場合の処理 \- Secret Garden\(Instrumental\)]( http://secret-garden.hatenablog.com/entry/2016/04/14/004729 )
* 問題なく表示されるが,`char*`として表示すると区切れる
* [c\+\+ \- Copy string data with NULL character inside string to char array \- Stack Overflow]( https://stackoverflow.com/questions/22907430/copy-string-data-with-null-character-inside-string-to-char-array )
## bit
### 10bit単位の数値
:warning: ビットフィールドの以下の性質は処理系定義であるので,移植性を考えると使うべきではないと思われる
[variables \- 10 or 12 bit field data type in C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/29529979/10-or-12-bit-field-data-type-in-c )
``` cpp
struct uint10_t {
uint16_t value : 10;
};
```
これはどのような仕様? => [ビットフィールド \- cppreference\.com]( https://ja.cppreference.com/w/cpp/language/bit_field )
`10bit`で表現できない数値を代入しようとすると警告が出る
```
xxx.cpp:22:11: warning: implicit truncation from 'int' to bit-field changes value from 2040 to
1016 [-Wbitfield-constant-conversion]
x.value = 0xFF << 3;
^ ~~~~~~~~~
1 warning generated.
```
### 10bit単位のデータをバイナリにパックしたい
[c\+\+ \- Efficiently packing 10\-bit data on unaligned byte boundries \- Stack Overflow]( https://stackoverflow.com/questions/34775546/efficiently-packing-10-bit-data-on-unaligned-byte-boundries )?
`64bit`中の下位`40bit`を利用して`10bit`x4個と`8bit`x5個として,まとめて計算して利用しようとしても,バイト単位で演算されて離れた箇所にデータが散らばり意図通りにならない(さらに,endianの問題も発生する)ため,結果として,単純にバイトごとに無理やり演算する方法が安全となり得る
下記のコードだが,シフト演算のみを行っているため,endianは関係ない気がしてきた
``` cpp
#include <bitset>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <vector>
/**
* @brief Pack 10bit data to 8bit array
*
* @param[in] upper_data 10bit data(2~9bit data) head pointer.
* @param[in] lower_data 10bit data(0,1bit data) head pointer.
* @param[in] data_size 10bit data array size
* @param[out] buffer 8bit array buffer head pointer(destination) required: 0 filled
*/
void Pack10bitPerByte(const uint8_t* upper_data, const uint8_t* lower_data,
const size_t data_size, uint8_t* buffer) {
int bit_offset = 0;
for (int i = 0; i < data_size; i++) {
uint32_t upper_offset = bit_offset % 8;
uint32_t lower_offset = (bit_offset + 2) % 8;
uint32_t upper_value = static_cast<uint32_t>(upper_data[i]);
uint32_t lower_value = static_cast<uint32_t>(lower_data[i]) & 0x03;
int byte_offset = bit_offset / 8;
// NOTE: set per byte (align is 2bit)
// NOTE: upper bit of upper data
buffer[byte_offset] |= static_cast<uint8_t>((upper_value >> upper_offset));
// NOTE: lower bit of upper data
if (upper_offset != 0) {
buffer[byte_offset + 1] |=
static_cast<uint8_t>((upper_value << ((8 - upper_offset) % 8)));
}
buffer[byte_offset + 1] |=
static_cast<uint8_t>(lower_value << ((8 - lower_offset) % 8));
bit_offset += 10;
}
}
/**
* @brief Pack 10bit data to 8bit array (only for little endian)
*
* @param[in] upper_data 10bit data(2~9bit data) head pointer.
* @param[in] lower_data 10bit data(0,1bit data) head pointer.
* @param[in] data_size 10bit data array size
* @param[out] buffer 8bit array buffer head pointer(destination)
*/
void Pack10bitPer40Bit(const uint8_t* upper_data, const uint8_t* lower_data,
const size_t data_size, uint8_t* buffer) {
int dst_offset = 0;
for (int i = 0; i < data_size;) {
int bit_offset = 0;
uint64_t tmp_calc_space = 0;
int pack_calc_max_n = (40 / 10);
pack_calc_max_n =
pack_calc_max_n < (data_size - i) ? pack_calc_max_n : (data_size - i);
for (int j = 0; j < pack_calc_max_n; i++, j++) {
uint64_t input_value = (static_cast<uint64_t>(upper_data[i]) << 2) |
(static_cast<uint64_t>(lower_data[i] & 0x03));
// NOTE: big endian
// int dst_bit_offset = bit_offset;
// NOTE: little endian
int dst_bit_offset = (40 - 10) - bit_offset;
tmp_calc_space |= input_value << dst_bit_offset;
bit_offset += 10;
}
// NOTE: for big endian
// for (int j = 0; j < bit_offset; j += 8) {
// NOTE: for little endian
for (int j = (bit_offset + (8 - 1)) / 8 * 8 - 8; j >= 0; j -= 8) {
buffer[dst_offset] = static_cast<uint8_t>((tmp_calc_space >> j));
dst_offset++;
}
}
}
int main(int argc, char* argv[]) {
std::vector<uint8_t> upper_data = {0x0F, 0x0F, 0x0F, 0x0F};
std::vector<uint8_t> lower_data = {0, 1, 2, 3};
std::vector<uint8_t> data(5);
std::vector<uint8_t> expected_data = {0x0f, 0x03, 0xd0, 0xf8, 0x3f};
int bit_offset = 0;
int byte_offset = 0;
int size = 4;
std::cout << "Pack10bitPerByte" << std::endl;
Pack10bitPerByte(upper_data.data(), lower_data.data(), size, data.data());
for (auto&& v : data) {
std::cout << (int)v << std::endl;
}
// NOTE: test
for (int i = 0; i < expected_data.size(); i++) {
if (data[i] != expected_data[i]) {
std::cout << "[" << i << "] data:" << (int)data[i]
<< "!= expected_data:" << (int)expected_data[i] << std::endl;
}
}
std::fill(data.begin(), data.end(), 0);
std::cout << "Pack10bitPerByte" << std::endl;
Pack10bitPer40Bit(upper_data.data(), lower_data.data(), size, data.data());
for (auto&& v : data) {
std::cout << (int)v << std::endl;
}
// NOTE: test
for (int i = 0; i < expected_data.size(); i++) {
if (data[i] != expected_data[i]) {
std::cout << "[" << i << "] data:" << (int)data[i]
<< "!= expected_data:" << (int)expected_data[i] << std::endl;
}
}
std::ofstream dst;
dst.open("output.raw", std::ios::out | std::ios::binary | std::ios::trunc);
if (!dst) {
std::cerr << "failed to write file: " << std::endl;
return 1;
}
dst.write((const char*)data.data(), data.size());
return 0;
}
```
ちなみに,`Doxygen`コメントの`in`,`out`は概念上の考え方に近い?(例えば,`&`での参照渡しを考慮したときに,それ自身の値が書き換わる場合と,そのポインタが指し示す値が書き換わる場合がある)
[documentation \- Is that an in or in/out parameter? Doxygen, C\+\+ \- Stack Overflow]( https://stackoverflow.com/questions/47732665/is-that-an-in-or-in-out-parameter-doxygen-c )
## シフト演算
* :warning: 意図せずにオーバーフローを引き起こす可能性がある(特に,定数値に対して処理を行う場合 `0xFF << x`とする際に,`uint32_t`の範囲を超えてしまう可能性があり得る)
* :warning: [INT34\-C\. 負のビット数のシフトやオペランドのビット数以上のシフトを行わない]( https://www.jpcert.or.jp/sc-rules/c-int34-c.html )
* 特に,__ビット数以上のシフト__が未定義であることに注意(ビット数以上のシフトを行うことで,`0`となることを意図するような設計に注意)
``` cpp
#include <cassert>
int main(int argc, char* argv[]) {
// NOTE: NG case: overflow(32bit << any bit)
assert(0x000000000 == (0xF0000000 << 4));
assert(0x000000000 == (0xF0000000 << 4UL));
assert(0x000000000 == (0xF0000000 << static_cast<int8_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint8_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<int16_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint16_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<int32_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint32_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<int64_t>(4)));
assert(0x000000000 == (0xF0000000 << static_cast<uint64_t>(4)));
// NOTE: OK case: 64bit << any bit
assert(0xF00000000 == (0xF0000000UL << 4));
assert(0xF00000000 == (0xF0000000UL << 4UL));
assert(0xF00000000 == (0xF0000000UL << static_cast<int8_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint8_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<int16_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint16_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<int32_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint32_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<int64_t>(4)));
assert(0xF00000000 == (0xF0000000UL << static_cast<uint64_t>(4)));
return 0;
}
```
## 64bit/32bitマシン別の型サイズ例
``` cpp
#include <cinttypes>
#include <iostream>
#include <string>
#define SIZEOF_PRINT(x) \
std::cout << "" #x << ":" << sizeof(x) << "B" << std::endl;
int main(int argc, char* argv[]) {
SIZEOF_PRINT(int);
SIZEOF_PRINT(long);
SIZEOF_PRINT(unsigned long);
SIZEOF_PRINT(unsigned long long);
SIZEOF_PRINT(uint64_t);
return 0;
}
```
```
# 64bitマシン
int:4B
long:8B
unsigned long:8B
unsigned long long:8B
uint64_t:8B
# 32bitマシン
int:4B
long:4B
unsigned long:4B
unsigned long long:8B
uint64_t:8B
```
## hash
intのhashのinputとoutputが一致してしまう(余計な演算なしにhashの定義を満たす)ため、`std::to_string()`をかますと意図する結果が得られる
* [c++ - Why std::hash\<int> seems to be identity function - Stack Overflow]( https://stackoverflow.com/questions/38304877/why-stdhashint-seems-to-be-identity-function )
* [c++ - hash value of int is the same number - Stack Overflow]( https://stackoverflow.com/questions/19734875/hash-value-of-int-is-the-same-number )
``` cpp
#include <functional> // for std::hash
#include <iostream>
#include <string>
int main() {
std::hash<std::string> string_hash;
for (int i = 0; i < 10; i++) {
std::cout << "" << i << ":" << string_hash(std::to_string(i)) << std::endl;
}
std::hash<int> int_hash;
for (int i = 0; i < 10; i++) {
std::cout << "" << i << ":" << int_hash(i) << std::endl;
}
}
```
```
0:10408321403207385874
1:11413460447292444913
2:17472595041006102391
3:11275350073939794026
4:2169371982377735806
5:16141698810441253349
6:10103374131519304989
7:5566429635965498611
8:14908220028612439606
9:8705150437960110905
0:0
1:1
2:2
3:3
4:4
5:5
6:6
7:7
8:8
9:9
```
## [c++11 - golang-style "defer" in C++ - Stack Overflow]( https://stackoverflow.com/questions/33050620/golang-style-defer-in-c )
``` cpp
#include <iostream>
#include <memory>
using defer = std::shared_ptr<void>;
int main() {
std::cout << "start" << std::endl;
defer _(nullptr, [](...) { std::cout << "Hello, World!" << std::endl; });
// or
// std::shared_ptr<void> defer(nullptr, [](...) { std::cout << "Hello, World!" << std::endl; });
// or
// std::shared_ptr<void> defer(nullptr, [](void*) { std::cout << "Hello, World!" << std::endl; });
// or
// std::unique_ptr<void, std::function<void(void*)>> defer(nullptr, [](void*) { std::cout << "Hello, World!" << std::endl; });
std::cout << "end" << std::endl;
return 0;
}
```
`...`は可変長引数であるが、直接`void*`を指定してもよい
[Variadic functions \- cppreference\.com]( https://en.cppreference.com/w/cpp/utility/variadic )
`std::unique_ptr`を利用する場合には、明示的に関数の型を記述する必要がある
## C
### `C`では,関数宣言にて`foo()`ではなく,`foo(void)`とする(`C++`では特に問題はない)
* [【C言語】引数なしの関数には void を書いた方がよいという話 \- 0x19f \(Shinya Kato\) の日報]( https://0x19f.hatenablog.com/entry/2019/04/17/213231 )
* [DCL20\-C\. 引数を受け付けない関数の場合も必ず void を指定する]( https://www.jpcert.or.jp/sc-rules/c-dcl20-c.html )
### [Cのプログラムでたまに出てくる関数内の無意味なvoid宣言の意味 \- $ cat /var/log/shin]( https://shin.hateblo.jp/entry/2013/03/13/175059 )
## 実現したいことからの逆引き
### 外部コマンドを安全に実行したい
[cpp\-examples/examples/fork\-exec at master · umaumax/cpp\-examples]( https://github.com/umaumax/cpp-examples/tree/master/examples/fork-exec )
### c++のコードの概要を知りたい
[Doxygen · Wiki · umaumax / memo · GitLab]( https://gitlab.com/umaumax/memo/-/wikis/Doxygen )
### vectorやmapなどのコレクションをダンプしたい
e.g. [neostream.hpp]( https://gist.github.com/umaumax/9765462a4a7e85dcb52515b7921191fd )
### 標準入出力先が端末かどうかを調べたい
``` cpp
#include <unistd.h>
#include <iostream>
int main() {
const bool stdin_console = isatty(STDIN_FILENO);
const bool stdout_console = isatty(STDOUT_FILENO);
const bool stderr_console = isatty(STDERR_FILENO);
std::cout << "stdin is" << (stdin_console ? "" : " not") << " terminal" << std::endl;
std::cout << "stdout is" << (stdout_console ? "" : " not") << " terminal" << std::endl;
std::cout << "stderr is" << (stderr_console ? "" : " not") << " terminal" << std::endl;
}
```
## 落とし穴(pitfall)
### [cos と std::cos は別物だっていう話 \- akihiko’s tech note]( https://aki-yam.hatenablog.com/entry/20080826/1219757584 )
* リンクの通り,`::cos(float)``double`となっている
* `using namespace std;`の有無で挙動が変化するので注意
* `x86``arm`(32bit)で`std::cos(float)`の計算結果が異なる
``` cpp
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <string>
#include <typeinfo>
#define FORMAT(msg, value) \
msg << (value) << " " << typeid(value).name() << std::endl
inline void test(float f_rad) {
double d_rad = static_cast<double>(f_rad);
std::cout << std::fixed
<< std::setprecision(std::numeric_limits<double>::max_digits10 + 1)
<< "float =" << f_rad << std::endl
<< "double =" << d_rad << std::endl
<< FORMAT("::cos(float) =", ::cos(f_rad))
<< FORMAT("::cos(double) =", ::cos(d_rad))
<< FORMAT("std::cos(float) =", std::cos(f_rad))
<< FORMAT("std::cos(double)=", std::cos(d_rad))
<< FORMAT("cos(float) =", cos(f_rad))
<< FORMAT("cos(double) =", cos(d_rad));
{
using namespace std;
std::cout << std::fixed
<< std::setprecision(std::numeric_limits<double>::max_digits10 + 1)
<< "using namespace std;" << std::endl
<< FORMAT("cos(float) =", cos(f_rad))
<< FORMAT("cos(double) =", cos(d_rad));
}
}
int main(int argc, char* argv[]) {
std::cout << "[test case]: 0.5235987902f" << std::endl;
test(0.5235987902f);
std::cout << std::endl;
std::cout << "[test case]: std::stof(\"0.5235987902\")" << std::endl;
test(std::stof("0.5235987902"));
return 0;
}
```
x86
``` log
[test case]: 0.5235987902f
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025388240814209 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025388240814209 f
cos(double) =0.866025396499206845 d
[test case]: std::stof("0.5235987902")
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025388240814209 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025388240814209 f
cos(double) =0.866025396499206845 d
```
arm
``` log
[test case]: 0.5235987902f
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
[test case]: std::stof("0.5235987902")
float =0.523598790168762207
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
```
``` diff
--- x86.txt 2021-02-01 18:08:12.539836723 +0900
+++ arm.txt 2021-02-01 18:08:15.675867962 +0900
@@ -3,12 +3,12 @@
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
-std::cos(float) =0.866025388240814209 f
+std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
-cos(float) =0.866025388240814209 f
+cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
[test case]: std::stof("0.5235987902")
@@ -16,10 +16,10 @@
double =0.523598790168762207
::cos(float) =0.866025396499206845 d
::cos(double) =0.866025396499206845 d
-std::cos(float) =0.866025388240814209 f
+std::cos(float) =0.866025447845458984 f
std::cos(double)=0.866025396499206845 d
cos(float) =0.866025396499206845 d
cos(double) =0.866025396499206845 d
using namespace std;
-cos(float) =0.866025388240814209 f
+cos(float) =0.866025447845458984 f
cos(double) =0.866025396499206845 d
```
### 暗黙的なbool変換について
`-Wall -Wextra -Wconversion`としても、warningとならない
``` cpp
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
// trueなので注意(アドレス値が!=0であるので)
// NG
bool x = []() { return false; };
// OK
// bool x = []() { return false; }();
std::cout << x << std::endl;
return 0;
}
```
### 暗黙的な型変換(float, double)
``` bash
g++ -std=c++11 a.cpp -Wall -Wextra -Wconversion
```
* `std::copy`を利用する場合、`std::vector<float>``std::vector<double>`では警告がでない
* `float``double`へ代入する際に警告がでないが、ユーザとしてはもともとdoubleで計算した結果を意図しているケースが多い
``` cpp
#include <iterator>
#include <vector>
int main(int argc, const char* argv[]) {
(void)&argc;
(void)&argv;
{
std::vector<float> f_vec = {1.0f, 2.0f};
std::vector<double> d_vec = {0.0, 0.0};
// no warning
std::copy(f_vec.begin(), f_vec.end(), d_vec.begin());
// no warning
std::copy(d_vec.begin(), d_vec.end(), f_vec.begin());
}
{
float f = 1.0f;
// 厳密には、ここで桁落ちしているわけではないので警告がでないと思われる
// しかし、意図していない操作である
double d = f;
// warning: conversion to ‘float’ from ‘double’ may alter its value [-Wfloat-conversion]
float f2 = d;
(void)&f;
(void)&d;
(void)&f2;
}
{
// warning: conversion to ‘float’ alters ‘double’ constant value [-Wfloat-conversion]
float f = 1.0f / 3.0;
(void)&f;
}
return 0;
}
```
## ファイル
### ファイルIOのベンチマーク
[c++ io benchmark]( https://gist.github.com/umaumax/1bde594a831c50f0d6d21a520209aad8 )
### ファイル/ディレクトリの存在確認
``` cpp
#include <fstream>
#include <string>
bool IsFileExist(const std::string &filename) {
std::ifstream ifs(filename);
return ifs.is_open();
}
```
* ファイル/ディレクトリが存在: true
* シンボリックリンクの場合はその参照先に依存
### 🌟ファイルを読みたい
* [How to read a file from disk to std::vector\<uint8\_t> in C\+\+]( https://gist.github.com/looopTools/64edd6f0be3067971e0595e1e4328cbc )
* [c++ - How to read a binary file into a vector of unsigned chars - Stack Overflow]( https://stackoverflow.com/questions/15138353/how-to-read-a-binary-file-into-a-vector-of-unsigned-chars )
* __なぜか、`std::istream_iterator`を利用すると、ファイルの読み込みサイズが小さくなってしまう現象が発生した__
* __理由は`\n`をスキップして`std::vector`へ保存してしまうためである__
* `std::istreambuf_iterator`には`uint8_t`は指定できない
``` cpp
#include <fstream>
#include <iostream>
#include <iterator>
#include <vector>
// LoadFileGood_istreambuf_iteratorと比較して、2倍ほど遅い
std::vector<uint8_t> LoadFileGood(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary);
if (file.fail()) {
return std::vector<uint8_t>();
}
file.unsetf(std::ios::skipws);
std::streampos fileSize;
file.seekg(0, std::ios::end);
fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> vec;
vec.reserve(fileSize);
vec.insert(vec.begin(), std::istream_iterator<uint8_t>(file),
std::istream_iterator<uint8_t>());
return vec;
}
// 🌟おすすめ
std::vector<uint8_t> LoadFileGood_istreambuf_iterator(const std::string& filepath) {
std::ifstream is(filepath, std::ios::in | std::ios::binary);
std::istreambuf_iterator<char> start(is), end;
std::vector<uint8_t> content(start, end);
return content;
}
std::vector<uint8_t> LoadFileBad_istream_iterator(const std::string& filepath) {
std::ifstream is(filepath, std::ios::in | std::ios::binary);
std::istream_iterator<char> start(is), end;
std::vector<uint8_t> content(start, end);
return content;
}
int main(int argc, const char* argv[]) {
std::string filepath("./main.cpp");
{
auto goodVec = LoadFileGood(filepath);
std::cout << goodVec.size() << std::endl;
for (auto& v : goodVec) {
std::cout << v;
}
std::cout << std::endl;
}
{
auto goodVec = LoadFileGood_istreambuf_iterator(filepath);
std::cout << goodVec.size() << std::endl;
for (auto& v : goodVec) {
std::cout << v;
}
std::cout << std::endl;
}
{
auto badVec = LoadFileBad_istream_iterator(filepath);
std::cout << badVec.size() << std::endl;
for (auto& v : badVec) {
std::cout << v;
}
std::cout << std::endl;
}
return 0;
}
```
### ファイルをmv(rename)する
* [std::rename \- cppreference\.com]( https://en.cppreference.com/w/cpp/io/c/rename )
* [FIO10\-C\. rename\(\) 関数の使用に注意する]( https://www.jpcert.or.jp/sc-rules/c-fio10-c.html )
移植性を考慮した場合,変更先が空ディレクトリは削除できるのでrename時にエラーが起こらないが,空ディレクトリではない場合はremoveが失敗する
``` cpp
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
int main() {
char oldname[] = "a.txt";
char newname[] = "b.txt";
// NOTE:
// remove file for avoiding std::rename implementation-defined
// If new_filename exists, the behavior is implementation-defined.
bool is_exist_newname_file = [](const std::string& filename) {
std::ifstream ifs(filename);
return ifs.is_open();
}(newname);
if (is_exist_newname_file) {
int remove_result = std::remove(newname);
if (remove_result != 0) {
std::cerr << "Error remove file '" << newname
<< "': " << std::strerror(errno) << std::endl;
return 1;
}
}
int rename_result = rename(oldname, newname);
if (rename_result != 0) {
std::cerr << "Error renaming file '" << oldname << "' to '" << newname
<< "': " << std::strerror(errno) << std::endl;
return 1;
}
return 0;
}
```
### ディレクトリ名取得/ディレクトリのfsyncを行う例
``` cpp
#include <errno.h> // strerror
#include <fcntl.h> // O_DIRECTORY, O_RDONLY
#include <unistd.h> // fsync, close
#include <cstring> // strerror
#include <iostream>
#include <string>
#include <tuple>
std::string Dirname(const std::string& filepath) {
const size_t pos = filepath.find_last_of("/");
if (std::string::npos == pos) {
return ".";
}
const std::string dirname = filepath.substr(0, pos);
if (dirname == "") {
return "/";
}
return dirname;
}
std::tuple<bool, std::string> SyncDir(const std::string& dirpath) {
std::string error_message{};
int fd = open(dirpath.c_str(), O_DIRECTORY | O_RDONLY);
if (fd == -1) {
error_message = std::string(strerror(errno)) + ": dirpath = " + dirpath;
return std::make_tuple(false, error_message);
}
int fsync_ret = fsync(fd);
if (fsync_ret == -1) {
error_message += std::string(strerror(errno)) + ": dirpath = " + dirpath;
}
int close_ret = close(fd);
if (close_ret == -1) {
error_message += std::string(strerror(errno)) + ": dirpath = " + dirpath;
}
return std::make_tuple(error_message.empty(), error_message);
}
int main(int argc, char* argv[]) {
bool ret{};
std::string error_message{};
std::tie(ret, error_message) = SyncDir("target_dir");
if (!ret) {
std::cerr << error_message << std::endl;
}
return 0;
}
```
### ファイルディスクリプタ
`<unistd.h>`
| プリプロセッサシンボル | value |
| -------- | -------- |
| STDIN_FILENO | 0 |
| STDOUT_FILENO | 1 |
| STDERR_FILENO | 2 |
[Man page of STDIN]( https://linuxjm.osdn.jp/html/LDP_man-pages/man3/stdin.3.html )
### 一時ファイル作成のイディオム
`open()`してから`unlink()`することで,削除し忘れを防ぐことができる
[ファイルを open したまま各種システムコールを呼び出すとどうなるか \| ゴミ箱]( https://53ningen.com/file-open-rename-unlink/ )
### `close(2)`
[【C言語】close\(\)失敗時にリトライするのは危険という話 \- Qiita]( https://qiita.com/_ydah/items/c5f6b42dbfb88c2fae1f )
### ファイルをseekして使い続ける例
``` cpp
ofs.clear();
ofs.seekg(0, std::ios::beg);
```
`std::ifstream`をずっと繰り返していると、メモリリークがあるのかもしれないと考えての実験用だが,特に問題なし
``` cpp
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
int main(int argc, char* argv[]) {
std::ifstream ifs("./main.cpp");
const int buf_size = 80;
char line[buf_size];
while (true) {
ifs.getline(line, buf_size);
ifs.clear();
ifs.seekg(0, std::ios::beg);
}
return 0;
}
```
## リストに処理をした結果に対して、何らかの処理をするが、共通する結果に対しては1回でまとめてもよい場合の例
``` cpp
#include <iostream>
#include <set>
#include <string>
#include <vector>
int main(int argc, char *argv[]) {
std::vector<std::string> hoge_list = {
"hoge",
"fuga",
"hoge",
};
std::set<std::string> hoge_set = {};
for (auto &&hoge : hoge_list) {
hoge_set.insert(hoge);
}
for (auto &&hoge : hoge_set) {
std::cout << hoge << std::endl;
}
return 0;
}
```
## ODR(one definition rule)
* ヘッダに実装を記述する際に,通常の関数は`inline`を付与する必要がある
* template関数はinline不要
* ただし,特殊化されたtemplateではinlineは必要
## assert
[ERR06\-C\. assert\(\) と abort\(\) の終了動作を理解する]( https://www.jpcert.or.jp/sc-rules/c-err06-c.html )
* `assert()``aboort()`を呼び出して、`SIGABRT`を送信するので、gdbで補足できる
* `NDEBUG`有効時にも、`gdb`でbacktraceを表示したい場合は`abort()`を利用すれば良い
## backtrace
[\[Linux\]\[C/C\+\+\] backtrace取得方法まとめ \- Qiita]( https://qiita.com/koara-local/items/012b917111a96f76d27c )
* glibc backtraceは`-ggdb3`では関数名が出力できず、`-rdynamic`を付加して動的ライブラリ化する必要があった
* 行数までは取得できない
### libunwind
``` bash
# for ubuntu
sudo apt-get install libunwind-dev
```
libunwindを利用すると、`-ggdb3``-rdynamic`なしでも関数名を取得可能
Mac OS Xではclangに取り込まれているので、そのまま利用可能(`-lunwind`も不要)
see: [llvm\-project/libunwind\.h at main · llvm/llvm\-project]( https://github.com/llvm/llvm-project/blob/main/libunwind/include/libunwind.h )
## clang++
### Ubuntuにてclang++で`cannot find iostream`となるときに、下記で直る
[c++ - clang++ cannot find iostream - Ask Ubuntu]( https://askubuntu.com/questions/1449769/clang-cannot-find-iostream )
``` bash
sudo apt install -y g++-12
```
## clang-format
ヘッダのソートなどの優先順位についての例: [clang-formatで*.generated.hを勝手にソートさせない方法]( https://zenn.dev/sgthr7/articles/14271d56253e7a )
## switchのcase中に変数宣言をするときの注意
caseごとではなく、switch単位でスコープとなるので、switchの範囲外で予め宣言するか、case内部に明示的にブロックを設ける必要がある
* [c\+\+ \- Getting a bunch of crosses initialization error \- Stack Overflow]( https://stackoverflow.com/questions/11578936/getting-a-bunch-of-crosses-initialization-error/11578973 )
* [c\+\+ \- What are the signs of crosses initialization? \- Stack Overflow]( https://stackoverflow.com/questions/2392655/what-are-the-signs-of-crosses-initialization )
## コピーやムーブをした場合でもデストラクタが呼ばれる
``` cpp
{
Hoge hoge;// コンストラクタ
hoge = Hoge("hello");// コンストラクタ, デストラクタ
// デストラクタ
}
```
[自作クラスをムーブする \- Qiita]( https://qiita.com/termoshtt/items/3397c149bf2e4ce07e6c )
## 実行時(共有ライブラリのロード時)に`undefined symbol: _ZTI8XXXClass(typeinfo for XXXClass)`
``` cpp
class XXXClass {
virtual void foo() = 0;
};
```
ここで`=0`としない場合は実体を定義する必要がある
ビルドは問題なく通り、実行時に発覚することが問題点
たしかに、共有ライブラリを`nm`コマンドで見てみると`U`となっている
* [c\+\+ \- What is an undefined reference/unresolved external symbol error and how do I fix it? \- Stack Overflow]( https://stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix )
* [Undefined symbols for architecture x86\_64:の原因(複数) \| MaryCore]( https://marycore.jp/prog/xcode/undefined-symbols-for-architecture-x86-64/ )
## 読み物
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(1\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040712/p1 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(2\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040712/p2 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(3\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040715/p1 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(4\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040724/p1 )
* [UNIX上でのC\+\+ソフトウェア設計の定石 \(5\) \- memologue]( https://yupo5656.hatenadiary.org/entry/20040725/p2 )
## tips
### std::endl
`std::endl`は実行毎に出力バッファの内容を逐一flushするので、実行時オーバーヘッドを減らすため、ループの途中などflushの必要がない箇所では改行文字を使用した方が良い。
``` cpp
for (auto i = start; i != end; ++i) std::cout << i << std::endl;
// =>
for (auto i = start; i != end; ++i) std::cout << i << '\n';
```
### std::map
`std::map`系のコンテナに対して`[]`演算子を用いて存在しないキーを参照した場合、valueをデフォルト値で初期化した上で要素を新規作成するという挙動である
### 出力用の参照渡し引数ではなく、返り値で出力を返すことが推奨される
``` cpp
void get_pos(Pos &out_pos) {
out_pos.x = 1;
out_pos.y = 2;
}
```
=>
``` cpp
struct Pos {
int x, y;
};
Pos get_pos() {
return {1, 2};
}
```
戻り値の構造体は自動的にmoveされるので、実行時オーバーヘッドは発生しない。
理想的には、RustのようにResult型を利用すると良い(ただし、デファクトスタンダードなライブラリが不明...)
### 基底クラスとなるクラスのデストラクタにはvirtualを付与すること
付与しないと継承先クラスを基底クラスへキャストしたときに呼び出されない
``` cpp
#include <iostream>
class Animal {
public:
void foo() { std::cout << "do foo" << std::endl; }
~Animal() { std::cout << "called base animal destructor" << std::endl; }
};
class Dog : public Animal {
public:
~Dog() { std::cout << "called derived dog destructor" << std::endl; }
};
class VirtualAnimal {
public:
void foo() { std::cout << "do virtual foo" << std::endl; }
virtual ~VirtualAnimal() {
std::cout << "called base virtual animal destructor" << std::endl;
}
};
class VirtualDog : public VirtualAnimal {
public:
~VirtualDog() {
std::cout << "called derived virtual dog destructor" << std::endl;
}
};
int main(int argc, const char* argv[]) {
std::cout << "start" << std::endl;
std::cout << "# dog" << std::endl;
{
Dog dog;
dog.foo();
}
std::cout << "# virtual dog" << std::endl;
{
VirtualDog dog;
dog.foo();
}
std::cout << "# derived dog" << std::endl;
{
auto derived = std::make_unique<Dog>();
std::unique_ptr<Animal> p = std::move(derived);
p->foo();
}
std::cout << "# derived virtual dog" << std::endl;
{
auto derived = std::make_unique<VirtualDog>();
std::unique_ptr<VirtualAnimal> p = std::move(derived);
p->foo();
}
std::cout << "end" << std::endl;
return 0;
}
```
``` cpp
start
# dog
do foo
called derived dog destructor
called base animal destructor
# virtual dog
do virtual foo
called derived virtual dog destructor
called base virtual animal destructor
# derived dog
do foo
called base animal destructor <=== 🔥 継承先のデストラクタが呼び出されていないことがわかる
# derived virtual dog
do virtual foo
called derived virtual dog destructor
called base virtual animal destructor
end
```
### 🔥クラスのメンバ変数はコンストラクタ引数の順序ではなく、メンバの定義順に初期化される
``` cpp
#include <iostream>
class ViewGood {
public:
ViewGood(int *start, std::size_t size)
: m_start{start}, m_end{m_start + size} {
std::cout << "view constructor: " << m_start << "," << m_end << '\n';
}
private:
int *m_start;
int *m_end;
};
class ViewBad {
public:
ViewBad(int *start, std::size_t size)
: m_start{start}, m_end{m_start + size} {
std::cout << "view constructor: " << m_start << "," << m_end << '\n';
}
private:
int *m_end;
int *m_start;
};
int main(int argc, const char *argv[]) {
{ ViewGood v(nullptr, 1); }
{ ViewBad v(nullptr, 1); }
return 0;
}
```
``` bash
$ g++ -std=c++20 main.cpp
main.cpp:18:31: warning: field 'm_start' is uninitialized when used here [-Wuninitialized]
: m_start{start}, m_end{m_start + size} {
^
$ ./a.out
view constructor: 0x0,0x4
view constructor: 0x0,0x1987fe3ca
```
### 🔥C++において引数の評価順は規定されておらず、評価順によって結果が変わるコードの動作は未定義である
引数で実行する関数の依存関係がなく順不同で実行されても問題がないようにすること
### [syncstream - cpprefjp C++日本語リファレンス]( https://cpprefjp.github.io/reference/syncstream.html )
[C++20便利機能の紹介:同期出力ストリーム std::osyncstream #C++ - Qiita]( https://qiita.com/yohhoy/items/b0fa779176d5debcd09e )
\ No newline at end of file