Skip to content

Commit e25e0cd

Browse files
committed
例を追加 #1106
1 parent 3141d83 commit e25e0cd

File tree

1 file changed

+202
-10
lines changed

1 file changed

+202
-10
lines changed

lang/cpp20/conditionally_trivial_special_member_functions.md

Lines changed: 202 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,217 @@ struct wrap {
6464
6565
## 例
6666
67-
(執筆中)
67+
`std::optional<T>`の簡易実装を考える。デストラクタについて、要素型`T`が[`trivially_destructible`](/reference/type_traits/is_trivially_destructible.md)であるかどうかに応じて`optional<T>`のデストラクタのトリビアル性も切り替えたい場合、次のような実装になる
68+
69+
```cpp
70+
// デストラクタがトリビアルでない場合のストレージ型
71+
template<typename T, bool = std::is_trivially_destructible_v<T>>
72+
struct optional_storage {
73+
union {
74+
char dummy;
75+
T data;
76+
};
77+
bool has_value = false;
78+
79+
// デストラクタは常に非トリビアルでdeleteされているので定義する
80+
~optional_storage() {
81+
if (has_value) {
82+
this->data.~T();
83+
}
84+
}
85+
};
86+
87+
// デストラクタがトリビアルである場合のストレージ型
88+
template<typename T>
89+
struct optional_storage<T, true> {
90+
union {
91+
char dummy;
92+
T data;
93+
};
94+
bool has_value = false;
95+
96+
// デストラクタはトリビアルであり常にdeleteされないので、宣言も不要
97+
};
98+
99+
template<typename T>
100+
class my_optional : private optional_storage<T> {
101+
public:
102+
103+
// 略
104+
...
105+
106+
// デストラクタ、この宣言も実はいらない
107+
~my_optional() = default;
108+
};
109+
```
110+
111+
無効値と有効値でストレージを共有し、なおかつ遅延初期化等を行いながら動的確保や追加のストレージ消費を抑えるためには共用体を使用する。共用体の特殊メンバ関数はそのメンバ型のもつ特殊メンバ関数が1つでも非トリビアルであるならば対応する特殊メンバ関数が`delete`されるため、その場合はユーザーが定義しなければならない。しかしそれを一つの型の中で行うことができなかったため、このようにデストラクタの実装を行うレイヤにおいて型を分割する必要があった。
112+
113+
これをこの機能を用いて書き直すと、次のような実装になる
114+
115+
```cpp
116+
template<typename T>
117+
class my_optional {
118+
bool has_value = false;
119+
union {
120+
char dummy;
121+
T data;
122+
};
123+
124+
public:
125+
126+
// 略
127+
...
128+
129+
// Tのデストラクタがトリビアルならばこちらが有効化
130+
~my_optional() requires std::is_trivially_destructible_v<T> = default;
131+
132+
// そうでないならばこちらが有効化
133+
~my_optional() {
134+
this->reset();
135+
}
136+
137+
// reset()の定義も同様の記法で分岐できる
138+
139+
void reset() requires std::is_trivially_destructible_v<T> {
140+
this->has_value = false;
141+
}
142+
143+
void reset() {
144+
if (this->has_value) {
145+
this->data.~T();
146+
}
147+
this->has_value = false;
148+
}
149+
};
150+
```
151+
152+
コンセプトによって`T`の(デストラクタの)トリビアル性に応じてデストラクタを選択できるようになるため、先ほどのような定義を分けるためのレイヤが不要になり、コード量を大きく削減することができる。
153+
154+
元の(この機能を使わない)実装に戻って、さらにコピーコンストラクタとムーブコンストラクタ、そしてコピー/ムーブ代入演算子についても同様に`T`のトリビアル性に応じて`default`とするかを切り替えることを考える。実装としてはデストラクタの時と同様に基底クラスにその判定と分岐を行うレイヤを追加して、そこで`T`の性質に応じた宣言(ユーザー定義/`default`定義)を記述していくことになるが、その総数はデストラクタのためのレイヤも含めて全5層に達し、実装はかなり複雑になる(このためここでは省略するが、この実装に興味がある場合、MSVC STLの[`xsmf_control.h`](https://github.com/microsoft/STL/blob/main/stl/inc/xsmf_control.h)およびこれを利用した`std::optional`/`std::variant`の実装が参考になると思われる)。
155+
156+
しかし、この機能を使用すると先ほどのデストラクタの場合と同様にそのような不要なレイヤを必要とすることなくシンプルに記述できる
68157
69158
```cpp example
70-
// (ここには、言語機能の使い方を解説するための、サンプルコードを記述します。)
71-
// (インクルードとmain()関数を含む、実行可能なサンプルコードを記述してください。そのようなコードブロックにはexampleタグを付けます。)
159+
#include <type_traits>
160+
#include <string>
161+
162+
template<typename T>
163+
class my_optional {
164+
bool has_value = false;
165+
union {
166+
char dummy;
167+
T data;
168+
};
169+
170+
public:
171+
172+
// デフォルトコンストラクタ
173+
constexpr my_optional()
174+
: has_value(false)
175+
, dummy{}
176+
{}
177+
178+
// 値を受け取るコンストラクタ
179+
template<typename U=T>
180+
constexpr my_optional(U&& v)
181+
: has_value(true)
182+
, data(std::forward<U>(v))
183+
{}
184+
185+
// トリビアルに定義できるならそうする
186+
my_optional(const my_optional& that) requires std::is_trivially_copy_constructible_v<T> = default;
187+
my_optional(my_optional&& that) requires std::is_trivially_move_constructible_v<T> = default;
188+
my_optional& operator=(const my_optional& that) requires std::is_trivially_copy_assignable_v<T> = default;
189+
my_optional& operator=(my_optional&& that) requires std::is_trivially_move_assignable_v<T> = default;
190+
~my_optional() requires std::is_trivially_destructible_v<T> = default;
191+
192+
193+
// そうでない場合はユーザー定義する
194+
195+
my_optional(const my_optional& that)
196+
: has_value(that.has_value)
197+
, dummy{}
198+
{
199+
if (that.has_value) {
200+
new (&this->data) T(that.data);
201+
}
202+
}
72203
73-
#include <iostream>
204+
my_optional(my_optional&& that)
205+
: has_value(that.has_value)
206+
, dummy{}
207+
{
208+
if (that.has_value) {
209+
new (&this->data) T(std::move(that.data));
210+
}
211+
}
74212
75-
int main()
76-
{
77-
int variable = 0;
78-
std::cout << variable << std::endl;
213+
my_optional& operator=(const my_optional& that) {
214+
auto copy = that;
215+
*this = std::move(copy);
216+
217+
return *this;
218+
}
219+
220+
my_optional& operator=(my_optional&& that) {
221+
if (that.has_value) {
222+
if (this->has_value) {
223+
this->data = std::move(that.data);
224+
} else {
225+
new (&this->data) T(std::move(that.data));
226+
}
227+
} else {
228+
this->reset();
229+
}
230+
231+
return *this;
232+
}
233+
234+
~my_optional() {
235+
this->reset();
236+
}
237+
238+
// reset()の定義も同様の記法で分岐できる
239+
240+
void reset() requires std::is_trivially_destructible_v<T> {
241+
this->has_value = false;
242+
}
243+
244+
void reset() {
245+
if (this->has_value) {
246+
this->data.~T();
247+
}
248+
this->has_value = false;
249+
}
250+
};
251+
252+
int main() {
253+
// int(全てのメンバ関数はトリビアル)の場合、my_optional<int>も同様になる
254+
static_assert(std::is_trivially_destructible_v<my_optional<int>>);
255+
static_assert(std::is_trivially_copy_constructible_v<my_optional<int>>);
256+
static_assert(std::is_trivially_move_constructible_v<my_optional<int>>);
257+
static_assert(std::is_trivially_copy_assignable_v<my_optional<int>>);
258+
static_assert(std::is_trivially_move_assignable_v<my_optional<int>>);
259+
260+
// std::string(全てのメンバ関数は非トリビアル)の場合、my_optional<std::string>も同様になる
261+
static_assert(std::is_trivially_destructible_v<my_optional<std::string>> == false);
262+
static_assert(std::is_trivially_copy_constructible_v<my_optional<std::string>> == false);
263+
static_assert(std::is_trivially_move_constructible_v<my_optional<std::string>> == false);
264+
static_assert(std::is_trivially_copy_assignable_v<my_optional<std::string>> == false);
265+
static_assert(std::is_trivially_move_assignable_v<my_optional<std::string>> == false);
266+
267+
// しかし、全ての特殊メンバ関数は利用可能(トリビアルでないだけ)
268+
static_assert(std::is_destructible_v<my_optional<std::string>>);
269+
static_assert(std::is_copy_constructible_v<my_optional<std::string>>);
270+
static_assert(std::is_move_constructible_v<my_optional<std::string>>);
271+
static_assert(std::is_copy_assignable_v<my_optional<std::string>>);
272+
static_assert(std::is_move_assignable_v<my_optional<std::string>>);
79273
}
80274
```
81-
* variable[color ff0000]
82275

83276
### 出力
84277
```
85-
0
86278
```
87279

88280
## この機能が必要になった背景・経緯

0 commit comments

Comments
 (0)