constinit, consteval, constexpr, const の違い
constexpr関数
constexpr関数はその関数がコンパイル時にも評価可能であることを表す。静的(コンパイル時)な評価を強制される箇所でなければ動的に実行される。constexpr関数とconstexprコンストラクタは暗黙的にinline指定される。
メンバ関数に用いる場合にC++11では暗黙的にconst修飾されていたがC++14以降const修飾されなくなった。
普通の関数に比べて制約は多くなるが規格が進むに連れて緩和されてきた。
constexpr int func(int a) { return a*a; } char ary[ func(16) ]; //コンパイル時定数が必要な場面で使用可能
constexpr変数
constexpr変数はその初期化がコンパイル時に行われコンパイル時定数であることを保証する。暗黙的にconstであるため変更不可能である。
constexpr int c = func(16); // 関数funcはconstexprもしくはconsteval指定されている必要がある // c = 2; //変更不可
constinit (C++20)
constinitは変数(static or thread storage)が静的に初期化されることを保証するもの。いわゆるグローバル変数などは初期化子が定数式かどうかによって初期化順序が異なる。
constinitを使うことによって意図せず動的に初期化されることを防ぎ、初期化順序によるバグ(動的初期化と静的初期化の違いに起因するもの)を防ぐことができる。暗黙的にconst指定されない。
constinit int g = func(); //関数funcはconstexprもしくはconsteval指定されている必要がある int main() { g = 2; //後から変更可。 }
consteval (C++20)
constevalはその関数が静的に評価される事を保証する。
即値ならぬ即時関数。constexpr関数とは異なり動的に実行されることはない。
consteval int func(int a) { return a*a; } int main() { int d = 2; func(d); //コンパイルエラー func(2); //OK }
const変数
const変数はそのオブジェクトが定数であり変更不可能であることを表す。組み込みの整数型など型と初期化子によってはコンパイル時定数となれる。
const int c = 1; //この場合const int型を整数リテラル1で初期化しているためコンパイル時定数になる // c = 2; //変更不可 int* const p = //...; //p = nullptr; //pは変更不可
const参照/constポインタ
const参照/constポインタはその 参照/ポインタ の指す先がその 参照/ポインタ を通して変更不可能であることを表す。
int i = 1; const int& c = i; // c = 2; //変更不可 i = 3; // c は 3になる const int * p = //...; // *p = 1; //変更不可 p = nullptr; //p自体は非const変数なので変更可
constメンバ関数
constメンバ関数はそのメンバ関数を持つクラスのオブジェクトに変更を加えない事を表明する。またそのオブジェクトがconst変数であってもconstメンバ関数は呼び出せる。
struct S { int func() const { //constメンバ関数 //m = 1; メンバ変数の変更不可 // これは this->m = 1; と同じ // this の型は const S* となる return m; } const int func2() { //戻り値がconst変数なだけで非constメンバ関数 m = 1; //変更可 return m; } int m = 0; }; const S s; s.func(); //constのオブジェクトsに対しても呼び出せる // s.func2(); //constのオブジェクトsに対して呼び出し不可
constexpr if文 (C++17)
if constexpr は条件をコンパイル時に評価するif文。テンプレート内で用いると条件によって実行されない文の実体化を防ぐことができる。
template <class T> void func() { if constexpr(false) { //ここの文は実体化されない T v; //もし依存名Tがvoid型でもコンパイルエラーとはならない } else { } }
consteval if文(C++23)
定数評価中に呼び出されているのかどうか判別できる。従来のif (std::is_constant_evaluated())の上位互換。consteval関数との相性改善や誤った使い方をしにくいことなどが利点。
constexpr int f() { if consteval { // コンパイル時評価されるときのパス } else { // 動的に実行されるときのパス } return 0; }
一般的なalias問題
前々回、前回と strict aliasing rule について紹介しましたが今回は一般的なalias(別名)の問題について紹介します。
以下の関数は深刻な問題を抱えています。お分かりいただけるでしょうか。
// x*2 + x*2を計算し結果をyに格納する。 ただしxは十分に小さな値とする。 void calc(unsigned& y, const unsigned& x) { y = x*2; y += x*2; }
コンパイラはソース通りにコードを生成するでしょう。しかしそれはコメントの仕様とは異なります。
コメントによれば x として 1 を入力すれば 4 が得られるはずですが以下のように呼び出した場合は 6 が得られます。
unsigned i = 1; calc(i, i); // i == 6
つまりxとyは同じオブジェクトを指しておりaliasの関係にあるため以下のように計算されていることになります。
unsigned i = 1; i = i*2; i += i*2;
x にはconstが付いていますがこのconstは x を経由した場合に変更できないだけであってyならば元のiはいくらでも変更可能です。
このcalc関数においてxとyがaliasの関係になりうることをプログラマが考慮していないために問題が起きています。コンパイラは同じ型(互換性のある型)同士aliasになり得ることを考慮したコード生成を行っています。
この例を解決するにはローカルな変数を利用するのがよいです(変数を使わず1行で書いてもよい)。最適化が効きやすくなる副次効果もあります。
void calc(unsigned& y, const unsigned& x) { unsigned tmp = x*2; y = tmp + x*2; }
さらに理想的なインターフェイスを目指すなら出力は戻り値にまとめましょう(複数の値を返す時は専用の構造体などを利用)。引数のunsigned intはサイズも小さいので値渡しにしましょう。値渡しならば原理的にエイリアスの問題とは無縁です。
int calc(unsigned x) { return x*2 + x*2; }
このエイリアスの問題はどんなところでも(また多くの別言語においても)起こり得る問題ですが真っ先に思い付くのは代入演算子における自己代入でしょう。
struct s { char arr[32]; s& operator = (const s& rhs) { //本来ならばわざわざ定義する必要もないですが memcpy(this->arr, rhs.arr, 32); return *this; } }; s v; v = v; //undefined behavior
上記コードのように自己代入した場合に(*thisとrhsは同一のオブジェクトであるから)同じアドレスをmemcpyに渡してしまい未定義動作となります。memmoveならば問題ありません。もしくはthisとrhsのアドレスを比較して同じ場合は処理を飛ばす手もあります。(自己代入ならば普通は何もしなくても結果は同じでしょう)。
他に自己代入でよくありがちなのはメモリリソースを手動管理している状況で、代入先のメモリを開放したら代入元のメモリまで開放されてしまい解放済みメモリへのアクセス&二重開放となる場合などです。
またシーケンスポイントがない場合にも注意しましょう。以下の例は未定義動作を引き起こします。
//組み込み型のint iをシーケンスポイントを挟まず複数回変更している int i=0; i = i++; //未定義動作
この場合まだ分かり易いですが以下のように名前が異なる場合は見落としやすくなります。
int i=0; int& j = i; i = j++; //未定義動作 func(++i, ++j); //未定義動作
常にaliasの存在(同一オブジェクトを指すポインタや参照の存在)を考慮し、aliasが存在しても問題の無いようにする必要があります。
strict aliasing rules, type punning解説 その2(union)
前回述べた strict aliasing rules の集成体とunionのルールについては分かりずらいです。自身も誤解していた部分があったので追加で解説を試みます。
このルールに該当するコードは以下のようものだと思っていましたが違うようです。
struct s { int i; }; static_assert(sizeof(s) == sizeof(int), ""); alignas(max_align_t) int a=0; //int型としてアクセス s* p = (s*)(void*)&a; s b = *p; //int型のオブジェクトaをs型としてアクセス OK? // (sはメンバにint型を含む集成体だから) // しかし以下のような例を考えると struct t { int i; }; static_assert(sizeof(t) == sizeof(int), ""); int f(s* p, t* q) { p->i = q->i + 5; return q->i; } int main() { alignas(std::max_align_t) int a=0; return f((s*)(void*)&a, (t*)(void*)&a); } // 関数f の g++ -O3 のアセンブリは // movl (%rdx), %eax // leal 5(%rax), %edx // movl %edx, (%rcx) // ret //rdxをリロードせずeaxをそのまま返している。 // gccとclangはこの例においてはエイリアシングを考慮していない。
gccとclangの仕様に合わせて解釈するのであればこのルールは以下のようにsとintの間にエイリアシングを考慮することを指していると考えられます。それぞれが元の型と同じ型でアクセスしているだけであり当然すぎる気もしますが最適化をするという趣旨から考えるとなるべくaliasingを考慮しないのは一番納得できる解釈です。現実のコンパイラ仕様からみるとプログラマは構造体Aの型Tのメンバと構造体Bの型Tのメンバの間にエイリアスが考慮されないとしてコーディングする必要があります。たとえ同じ型Tであっても別々の構造体のメンバであることがわかる状況であればエイリアスは考慮されないのです!
s a{};
func(&a, &a.i);
void func(s* x, int* y) {
*x = s(); //yの指す先にも変更が及ぶ可能性が考慮される。
int b = *y; //OK sとintのエイリアスを考慮。
}
//gccとclangの挙動をまとめると struct s { int i; }; struct t { int i; }; void func(s* x, int* y); //sとintのエイリアシングは考慮される void func(s* x, t* y); //sとtのエイリアシングは考慮されず、 //sのintメンバとtのintメンバはint型同士であってもエイリアシングは考慮されない。
unionについても集成体と同じくルールが及ぶのは以下のような場合であると考えられます。
union u { int i; }; u a; func(&a, &a.i); void func(u* x, int* y) { *y = 0; //xの指す先にも変更が及ぶ可能性が考慮される。 u b = *x; //OK uとintのエイリアスを考慮。 (uはメンバにint型を含むunionだから) //規格ではOKだと思うがgccではunionのメンバをポインタ経由で触っているの //でundefined behaviorとなる。 }
ではunionのメンバについてはどうでしょうか
前回紹介した通り以下の例は未定義動作となります(gccもしくはC99以降ではOKですが)。
union u { int i; float f; }; u a; a.i = 0; //int型としてアクセス float v = a.f; //上記のint型のオブジェクトをfloat型としてアクセス undefined behavior
では以下の例はどうでしょうか
union u { int i; float f; }; u a; a.i = 0; //int型としてアクセス a.f = 0; //上記のint型のオブジェクトをfloat型としてアクセス? // ん?intとfloatの間にエイリアシングは考慮されないのでは?
かなり怪しく思えますが、この例の場合は a.f = 0; の時に以前のint型のオブジェクトが破棄され新しくfloat型のオブジェクトを構築したと考えればstrict aliasing rulesに矛盾しないように思えます。つまり一般的にいうvariant型のようにある時点で単一のオブジェクトが格納されていて、その寿命期間中にそのオブジェクトの型としてしか扱わないのであれば問題ないように思えるし、規格にもそれを前提としているような例がみられます。
ただし、両方のメンバのポインタを取る例を考えると途端に雲行きが怪しくなります。以下のような例ではコンパイラはunionのメンバを破棄、構築したとは考えないでしょう。
u a; func2(&a.i, &a.f); void func2(int* x, float* y) { *x = 0; *y = 0; //まさかint型とfloat型が同じアドレスを指しているとはコンパイラは夢にも思わないだろう。 //そしてエイリアシングが存在しない前提で最適化を行ってしまい // _人人人人人人人人人人_ // > 突然の未定義動作 < //  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ }
unionのメンバのstructがcommon initial sequenceを持つ場合
C99では特別な保証として以下のようなことが有効であるとされています(ただしunionの完全型が見えている場所に限られる)。
union { struct { int m; } s1; struct { int n; } s2; } u; u.s1.m = 1; int v = u.s2.n;
しかしこのルールはC++ではNoteになっています。またC99にあるunionの完全型が見えている場所という条件は付いていませんがそもそもNoteなのであまり当てにはできません。
さて所変わってC++11からはコンストラクタ、デストラクタを持つようなクラスもunionのメンバとして使用できるようになりました。このメンバを使うには明示的なデストラクタの呼び出しと配置newが必要になります。配置newを使用するにはunionのメンバのポインタを取る必要があるのですがこれはgccが未定義動作としています。一方で新しいgccはこのC++11のunionをサポートしたと謳っているのでこの配置newを使う場合に限ってはunionのメンバのポインタを取ることは問題ないと考えられます。
strict aliasing rules, type punning解説 その1
strict aliasing rules つまり元の型のオブジェクトを別の型のオブジェクトとして使用すること(type punning)は基本的にできないというC/C++におけるルール。
つまり以下のようなことはできず、未定義動作を引き起こします。
float value = 0; int i = *(int*)&value; //undefined behavior
これはfloatとintに互換性(aliasingの仮定)がないからです。ここではアライメントや型の大きさの違いといった環境依存の別の問題も存在しますが、これをクリアしても依然として未定義動作です。
static_assert(sizeof(float) == sizeof(int), ""); alignas(float) alignas(int) float value = 0; int i = *(int*)&value; //undefined behavior
例ではポインタを用いましたが参照でも同じです。
以下この記事ではstrict aliasing ruleに焦点を絞って解説します。アライメントや型の大きさ、trivially copyableかどうか、effective type、ポインタ型のキャスト、内部表現(パディング、エンディアン、2の補数?、IEEE754?、C言語におけるtrap representation)といったその他のことについてはあまり考慮していません。つまりこの記事でOKとしてあっても上記の理由により未定義動作や未指定動作となることが十分にあり得ます。実際にtype punningを行う際にはこれ等にも注意する必要があります。またこの記事における未定義動作という用語は規格で述べられたもの、個々のコンパイラのマニュアルで述べられたもの、個々のコンパイラが期待とは異なるコード生成をする事などの意味を混同して使用していますのでその点をご留意ください。
このルールの背景にあるのはaliasing(別名の存在)を仮定しないことによりある意味では普通の最適化をしたいという動機です。
下記例は strict aliasing rules の存在によって最適化が可能な一例です。
void fill(int* dest, double* src, size_t count) { for (size_t i=0; i<count; ++i) { int value = *src; *dest++ = value; } }
destの指す先が更新されてもsrcの指す先に変更は及ばない(intとdoubleに互換性はないからint*とdouble*は同じ領域を指してはいないだろう)と考えられるため int value=*src; をforループに入る前の位置へ移動する最適化が可能です。もし同じ領域を指すと仮定すると(aliasingが存在していたら)最適化はできなくなります。
通称 strict aliasing rule について規格では大よそ次のように述べられています。
左辺値の元の型のオブジェクトを以下に述べる型以外でアクセスした場合は未定義動作である。
- 元の型に対してsigned,unsignedの関係にある型。
- const等で修飾された型。
- type similarな(cv修飾だけが違うようなポインタ)型(C++11)。
- 継承関係にある型。
- 上記の型を含んだ集成体(配列、条件付きの構造体)、union。
- char, unsigned char, std::byte(C++17)型。C言語ではsigned charも含まれるがC++では含まれない。
つまりこれらがaliasingの存在が考慮される型であり互換性があると言えます。特にchar,unsigned char,std::byte型はどんな型に対してもaliasingが仮定されます。また特に集成体とunionについては誤解が多い所で注意が必要です。左辺値と条件が付いていますがルールのイメージを掴む上で必要なものではないのでとりあえず忘れてもらって構いません。
unsigned value = 0; int i = *(const int*)(void*)&value; //OK
unsigned intとconst intは互換性がある(ただし非constからconstへの一方向です)。このときストレージのサイズとアライメントについては心配いりません。標準のsigned型とunsigned型はどちらも同じです。
float value = 0; char* tmp = (char*)&value; int i = *((int*)tmp); //undefined behavior
途中でchar*型を経由しても意味はありません。元の型とそれをアクセスする型はそれぞれfloatとintであり互換性がありません。
aliasingを仮定した適切なコードにするにはどうするかというとchar,unsigned char型としてアクセスし用意した別の領域へコピーするようにします。memcpyやmemmoveを使用するのが一番汎用的な方法でしょう。
static_assert(sizeof(float) == sizeof(int)); float value=0; int out; memcpy(&out, &value, sizeof(out)); //もしくは char* src = (char*)(void*)&value; char* dest = (char*)(void*)&out; for (int i=0; i<sizeof(out); ++i) *dest++ = *src++;
memcpyの仮引数はvoid*型であり一見aliasingは仮定されないように思うかもしれませんが標準ライブラリの<string.h>ヘッダの全ての関数は内部でバッファをunsigned char型として扱うと決められているのでこれらの関数は常にaliasingを仮定しているため安全です(ちなみにこの記事におけるaliasingとは型同士の話であって、memmoveとmemcpyの差異というのは少し違った話です)。逆に言えばコンパイラがmemcpyやmemmoveを積極的にinline化してくれるとしてもaliasingの存在を仮定しているという点においては遅くなる可能性があります。また単にchar,unsigned char型を文字列としてしか扱っていなくてもその個所では最適化が効きづらくなっていると考えられます。
char,unsigned char配列型のストレージを単独の別の型(T)として扱う場合は規格のstrict aliasing ruleに該当する文面を見る限りでは保証されていないと考えられます。が結果的にchar,unsigned char型は任意の型Tと相互にaliasingが仮定されることになるため最適化に絡む問題が発生する可能性は少なくなりそうです(もちろんアライメント等の別の問題が発生する余地は十分にあります)。しかし互換性のない2種類以上の型として使用した場合は明らかに問題があるでしょう。
(規格ではchar,unsigned char配列型へtrivially copyableな型の値をコピーしてからまた元に戻せることは保証していますがここでは直接関係なさそうです)
未定義動作の避け方ですがバイト列のデータをT型のオブジェクトとして読み取りたいならmemcpyを使い、char配列のストレージ上にT型のオブジェクトを構築したいなら 配置new を使用するのが良いでしょう。
規格ドラフトN4140から抜粋した以下の文には"reused"や"with side effects"など思わせぶりな単語が見えるので
3.8 Object lifetime
The lifetime of an object of type T ends when:
— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
— the storage which the object occupies is reused or released.
3.7.3 Automatic storage duration
If a variable with automatic storage duration has initialization or a destructor with side effects, it shall not
be destroyed before the end of its block,
配置newによってreusedされ以前のcharオブジェクトが破棄されたと考えればstrict aliasing rulesは回避できそうな気がします。
alignas(T) unsigned char storage[sizeof(T)]; *((T*)&storage) = T{}; // undefined behavior T* p = ::new ((void*)&storage) T(); //OK *p;// OK p->~T();
エイリアシングの考慮による最適化の違い clang3.3 -O2
void fill(int* dest, double& src, size_t count) { for (size_t i=0; i<count; ++i) { int value = src; *dest++ = value; // #1 int,unsigned,char,unsigned charのみ考慮 //memcpy(dest++, &value, sizeof(int)); // #2 あらゆる型を考慮 } }
# 1 cvttsd2si (%rsi), %eax #呼び出されるのは最初だけ .LBB0_2: movl %eax, (%rdi) addq $4, %rdi decq %rdx jne .LBB0_2 # 2 .LBB0_1: cvttsd2si (%rsi), %eax #ループ内で何回も呼び出される movl %eax, (%rdi) addq $4, %rdi decq %rdx jne .LBB0_1
またこれとは関係なく一般的に関数呼び出しをしていればその先でsrcの指す先が更新される可能性があるため最適化の難易度は上がります。
//ポインタの最下位ビットを立てる例(そもそも推奨できませんが) typedef void* pointer; static_assert(sizeof(pointer) == sizeof(uintptr_t), ""); alignas(max_align_t) pointer p = nullptr; // undefined behavior *(uintptr_t*)(&p) |= 1; // OK uintptr_t tmp; memcpy(&tmp, &p, sizeof(tmp)); tmp |= 1; memcpy(&p, &tmp, sizeof(tmp));
unionを使用する方法はお勧めできません。strict aliasing ruleによれば未定義動作となります。strict aliasing rule の文面にunionとありますがこれはunionのメンバ同士のtype punningを意味するのではありません。
gccにおいてはunionのメンバを直接使用する分には問題がないが、ポインタ(参照も同様でしょう)を経由した場合はundefined behaviorであると記載されています。特にunionのメンバのポインタ(参照も)をとることは他のコンパイラにおいても少なからず制限されると考えられます。
C99以降ではunionを使用した際のstrict aliasing ruleによる未定義動作は解消されていますがこの自由度はgccと同程度のものと考えたほうがいいでしょう。
union uni{ float f; int i; } u; u.i = 0; float* p = &u.f; *p = 0; //the behavior is undefined on gcc and probably on other compilers. alignas(uni) int a=0; ((uni*)&a)->f = 0; //the behavior is undefined on gcc.
unionが利用できる場面は実際的にはかなり限定されていると考えたほうがいいでしょう。unionはtype-punningのためのものではないのです。
応急的な回避策としてはあらゆる型同士の間にaliasが存在すると仮定するgcc、clangのコンパイルオプション-fno-strict-aliasingが利用できます。
ただコード自体が規格に沿うわけでもありませんし、strict aliasing rulesに基づく最適化もされなくなります。それでもundefined behaviorよりはよっぽどましです。
世の中にはstrict aliasing rulesを無視したコードが山のように存在しているため-fno-strict-aliasingオプションを積極的に使っていきましょう。
SiSグラフィック搭載機にLinuxをインストールするメモ
原因
X.orgのSiSドライバ内においてモニタのEDID解釈部分でセクションの拡張を考慮していないためにSEGVする。
対処
インストーラが起動する前の画面が紫色の場面で何かキーを押して起動オプションにxforcevesaを付け加えてVESAドライバを使用する。インストール後はshiftキーでgrubメニューを表示させてeで起動オプションに「text」を付け加えてテキストモードで起動し、VESAドライバを使用するようにしたいので/etc/X11/xorg.confを管理者権限で作成編集して
Section "Device" Identifier "XXXX" Driver "vesa" EndSection Section "Screen" Identifier "YYYY" Device "XXXX" EndSection
となるようにする。これで常にVESAドライバが使用されるようになる。
もしくはEDIDの拡張部分を使用しない別のモニタに替えるという物理的な方法もおそらく使える。
起動時にはモニタを接続せずに必要になった時だけモニタを接続するという方法も?
3Dサポートとパフォーマンスについて
Linuxにおいて3Dをサポートするには別途DRIドライバが必要になるが、多くのSiSチップセット向けにDRIドライバは存在しない。
なので3Dサポートは望めずUnityは重く実用にならない。結局xubuntu-desktopなどを導入することになる。
Fixed ドライバ
それでもSiS用が使いたい人のためにEDIDバグを修正したドライバを用意しました。
GitHub - gununu/xf86-video-sis-stable: Driver for SiS with stability fixes. Tested on sis662. Forked from https://gitorious.org/xf86-video-sis671/sis-671-fix Threre is no maintenance activity except compilation error fixes.
実際にはいろいろと悪戦苦闘してUbuntuのmini.isoからテキストモードインストーラを使用しました。現在ドライバにはこのsisimediaドライバを使用しています。
このドライバはantiXというディストリビューションに取り込まれたらしいので手っ取り早くSiSチップ搭載機上でLinux環境を用意したいならUbuntu系列ではなくantiX 15以降を使用するのがいいのかもしれません。
インストールやデバッグに使用した便利なコマンドなど
管理者権限でコマンドを実行する
sudo XXX
パッケージのインストール
sudo apt-get install XXX
パッケージの検索
apt-cache search XXX
ホームディレクトリへ移動。 ~はホームディレクトリを表す
cd ~
パスやコマンド名の補完
TABキー
カレントディレクトリのファイルを表示 -aは.から始まる隠しファイルを表示
ls -a
カレントディレクトリにあるファイルの実行 ./から始める
./ファイル
テキストエディタ。Vimに慣れていないならnanoが簡単
nano FILE
ファイルのコピー
cp FROM TO
リネーム
mv OLDNAME NEWNAME
ファイルの削除 ディレクトリを再帰的に削除するにはrm -r
rm FILENAME
プロセスの表示
ps alx
プロセスの終了
kill PID
Xの終了
sudo service lightdm stop
再起動
sudo reboot
シャットダウン
sudo shutdown -h now
イマドキのメンバ変数のリソース管理とPImpl
クラスにメンバ変数を持たせるには
struct Name {
Type member;
};
これが普通ですが時には不満が出てきます。例えばメンバ変数の生成タイミングを遅らせたいなど、クラス寿命の範囲内でメンバ変数の寿命を管理したい場合や、#include する Type の定義が重すぎてコンパイル時間が延びるので PImplイディオム を使用してヘッダ間の依存性を減らしたい場合、またはクラスの定義が循環していてコンパイルできない場合などに不完全型のメンバを持ちたい場合です。
寿命管理を実現したい場合
まず寿命管理と聞いて思い浮かぶのがstd::unique_ptrやstd::shared_ptrですがそれぞれ問題があります。まずunique_ptrですがこれをメンバに持つとクラスがコピーできなくなってします。これを不満に思う人は結構いると思います。これはコピーコンストラクタやコピー代入演算子を自分で定義すれば解決できますが、デストラクタをRAIIに任せて自分でなるべく書くべきではないのと同じくこれらの関数も自分で書くのは間違いや保守性の低下の元ですから避けたいところです(代入演算子については自分で書いたほうが例外安全の観点からは良いことも多くありますが)。むしろまともなRAII対応メンバを持っていればデストラクタを自分で定義していてもデメリットらしいデメリットは無い一方、コピーコンストラクタや代入演算子等は常にメンバの追加に対して一般的に保守が必要になるので厄介な存在と言えます。またshared_ptrはコピーができますがこれはshallow copyなので意図した挙動とは異なる場合も多いです。そこで登場するのが我らboost.optionalです。optionalならdeep copyですから直観的な挙動と同じになってくれます。インターフェイスもポインタライクでunique_ptrなどからの置き換えも容易です。しかしながら問題点もあります。それはmove非対応なことです。イマドキそれはないよね。ということでstd.optionalに期待がかかるわけですがどうやら当分先のことになりそうです。最後の希望はexperimental/optionalですがどの環境でも使用できるかは疑問なので積極的に使う気になれません。
そんなわけでstd.optionalが来るまでは下記の拙作pimplイディオムライブラリを使用しての解決をおすすめしてみます。
追記: boost 1.56.0よりoptionalがmoveをサポートしました。
標準ライブラリだけを使用するならvectorなどのコンテナを薄くラップして使う方法も考えられます。
ダメな例
struct Name { Type* member = nullptr; //生ポインタは論外! ~Name() { delete member; } void Prepare() { delete member; member = new Type{}; } void Use() { if (member) Type& r = *member; } }; void f() { Name a,b; a.Prepare(); b.Prepare(); a = b; //a.memberのポインタがリーク } //破棄時に二重deleteで未定義動作
#include <memory> struct Name { std::unique_ptr<Type> member; void Prepare() { member.reset(new Type{}); } void Use() { if (member) Type& r = *member; } }; void f() { Name a,b; a = b; //コピーできない }
#include <memory> struct Name { std::unique_ptr<Type> member; Name() = default; //間違いの元、保守性の低下 Name(Name const& rhs) : member(new Type(*rhs.member)) {} Name& operator = (Name const& rhs) { this->member.reset(new Type(*rhs.member)); } void Prepare() { member.reset(new Type{}); } void Use() { if (member) Type& r = *member; } };
解決策
#include <boost/optional.hpp> struct Name { boost::optional<Type> member; void Prepare() { member = Type{}; } void Use() { Type& r = member.value(); } };
#include <vector> struct Name { std::vector<Type> member; void Prepare() { member.assign(1, Type{}); } void Use() { Type& r = member.at(0); } };
不完全型を扱いたい場合
クラスの定義が循環して(木など再帰的な構造)いてメンバが不完全型であるためにコンパイルできないという状況はよく発生します。このような状況ではメンバにポインタ型(ポインタ自体は完全型)を持たせそれを経由させるのが回避策となりますが、上記と同じような問題が発生します。またこの場合には標準ライブラリのコンテナも回避策には使えません(将来的には不完全型が扱えることを保証する方向のようですが)。そこで下記の拙作pimplイディオムライブラリを使用しての解決をおすすめしてみます。注意点としてはデフォルトでオブジェクトが構築されるので定義が循環したクラスだとメモリを食い尽くしてしまいます。そこでpimpl_noinitと組み合わせて必要に応じてオブジェクトを生成してください。
PImplイディオムを実現したい場合
ヘッダ間の依存を減らすPImplイディオムとしてunique_ptrを使用した場合は上記の問題に加えてクラスのコンストラクタとデストラクタをソースファイル側に記述する必要性が生まれてしまいます。shared_ptrを使用した場合はデストラクタを記述する必要はありませんが挙動がshallow copyになってしまいます。またunique_ptr、shared_ptr共にdeep copyするにはソースファイル側にコピーコンストラクタや代入演算子を定義する必要が出てきてしまいます。またoptionalはpimplとは無縁の存在です。
そこでpimplライブラリを作ってみました。このライブラリの利点はdeep copyで値セマンティクスを実現し、move対応、必要なのはソースファイル側でコンストラクタを定義するか値を構築するだけです。もちろんヘッダ間の依存関係を減らすことができます。
#include "pimpl.hpp" class Type; //前方宣言のみ ヘッダのインクルードや定義はソースファイル側で struct Name { Name(); //コンストラクタ定義はソースファイル側で gununu::pimpl<Type> pmember; }; //値の構築を遅らせるならコンストラクタ定義不要。 //値の構築はソースファイル側で struct Name { gununu::pimpl<Type> pmember = gununu::pimpl_noinit; };
//ソースファイル側 #include "Name.hpp" //ここでTypeの定義をincludeしたり、直接定義する。 //複数のメンバはまとめて即席の構造体を定義するのもよい。 //namespaceの絡む前方宣言は面倒なので単独の型でも構造体でラップした方が楽 struct Type { Heavy a, b; }; Name::Name() = default; //値の構築はソースファイル側で行われる必要がある //Typeを実際に使用するときはソースファイル側の関数で void Name::SomeFunc() { pmember->a; pmember->b; }
PImplイディオムとして使用するつもりが無ければ前方宣言、コンストラクタ定義やソースファイル側での構築の必要はありません。
#include "pimpl.hpp" struct Name { gununu::pimpl<Type> pmember; //デフォルトで値が構築されるので構築したくない場合は //gununu::pimpl<Type> pmember = gununu::pimpl_noinit; };
現状optionalやスマートポインタとは違いデフォルトで値が構築されるのですが挙動を合わせたほうがいいのだろうか。
ライブラリ作成のコツ
普通にヘッダに関数を書くとinline化されて利用者側でも完全型が要求されpimplライブラリとして成り立たない。そこでinline化を回避する方法としてvirtual関数を使用している。
NGINXでlocalhost:80のサーバーを立てるメモ
設定ファイルは /etc/nginx/nginx.conf この中に include /etc/nginx/sites-enabled/*; としている箇所があり、./sites-enabled/default を読み込んでいる defaultにはlocalhost:80の設定がある。なので#でコメントアウトして#include /etc/nginx/sites-enabled/*;とする。
include /etc/nginx/conf.d/*.conf;という行もあるので ./conf.d/に.conf拡張子の設定ファイルを作り以下のように設定する。rootはドキュメントルートを指定する。このフォルダにhtmlなどを入れておく。当然ながら外部に公開されることになると思うのでそこは注意。
server {
listen 80;
server_name _;
root /home/USERNAME/web;
}nginxの起動 sudo nginx
nginxの設定ファイル再読み込み sudo nginx -s reload
nginxの終了 sudo nginx -s quit
nginxのバージョンとコンフィグを表示 nginx -V