Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
C++ はなぜあんなにも複雑なのか
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
WATANABE Yuki
February 02, 2022
Programming
99
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
C++ はなぜあんなにも複雑なのか
WATANABE Yuki
February 02, 2022
More Decks by WATANABE Yuki
See All by WATANABE Yuki
速いクイックソート: Pattern-defeating quicksort
magicant
0
470
鉄道シミュレーターで自動運転を実装した話
magicant
0
380
ドキュメント、書けてますか?
magicant
0
130
Other Decks in Programming
See All in Programming
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
240
信頼性の目標を誰も求めてない
shubox
0
510
ハーネス設計入門 〜プロンプト、コンテキストの次〜
kinopeee
56
38k
PHPプロジェクトの結合バランスを可視化する #php_night
kajitack
0
240
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
320
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
440
Claude Codeを組織的に動かして月400PRを実現した話
happy_ryo
0
270
ALB ログから Trace を気合で繋げる技術
fohte
7
890
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
510
【高い買い物LT会】初任給で話題の国産フィジカルAIを買った話
akagami
PRO
0
150
AIエージェント時代のコードレビューを設計する
nogu66
6
2.7k
XHTMLが残したもの
yosuke_furukawa
PRO
2
710
Featured
See All Featured
Bash Introduction
62gerente
615
220k
My Coaching Mixtape
mlcsv
0
310
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
500
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
1
400
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.6k
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
2
430
Crafting Experiences
bethany
1
330
Paper Plane (Part 1)
katiecoart
PRO
1
11k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.9k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.6k
The SEO Collaboration Effect
kristinabergwall1
1
560
Transcript
C++ はなぜあんなにも複雑なのか 渡邊裕貴 / 2022-02-02 Rust との比較で考える
近況 最近、趣味で Rust を書いてます Rust に慣れるともう C++ は書きたくなくなるね
Rust と C++ の違い • 開発環境のエコシステム • 所有権と借用の管理 • ムーブセマンティクス
• 型安全な並行計算 • 例外とエラー処理 • …… 今日のお題 気が向いたら次回話す 多分一番有名
Rust のオブジェクトは動かせる struct S; impl Drop for S { fn
drop(&mut self) { println!("{:p}", self); } } fn main() { let a = S; let b = a; // ここで b のデストラクターだけが実行される }
C++ のオブジェクトは動かない struct S { ~S() { std::cout << this
<< std::endl; } }; int main(void) { auto a = S(); auto b = a; // b と a のデストラクターが実行される }
Rust はメンバーを移動してオブジェクトを生成する struct Person { name: String, age: i32, }
fn main() { let name = String::from("わたなべ"); let age = 36; let developer = Person { name, age }; let manager = developer; }
C++ はコンストラクターでオブジェクトを生成する struct Person { std::string name; int age; Person(const
char *name, int age) : name(name), age(age) {} Person(const Person &p) : name(p.name), age(p.age) {} }; int main(void) { Person developer("わたなべ", 36); Person manager(developer); }
オブジェクトの生成と移動 Rust 1. オブジェクトの移動が定義されている ◦ 自動的・暗黙的に 2. 代入によって オブジェクトを移動する 3.
移動によって オブジェクトを生成する コンストラクターの概念はない C++ 1. コンストラクターが定義される ◦ 暗黙的に または明示的に 2. コンストラクターを呼ぶことで オブジェクトを生成する 3. 新しいオブジェクトを生成することで 実質的にオブジェクトを移動する ここまでのまとめ
コピー & ムーブ
Rust では移動した後の変数は使えない let developer = Person { name, age };
let manager = developer; println!("{}", developer.name); // エラー!
Rust でオブジェクトを複製したいときは明示的に let developer = Person { name, age };
let manager = developer.clone(); println!("{}", developer.name); // OK
いちいち clone 関数を実装するのはめんどい マクロを使えば自動的に実装される #[derive(Clone)] // ←これを付けるだけで clone() が呼べるようになる struct
Person { name: String, age: i32, }
いちいち clone を呼ぶのもめんどい • #[derive(Copy)] を付けると clone() が自動で呼ばれるようになる • 整数型などの「軽い」型で有効
let x = 0; let y = x; // x.clone() とは書かなくて良い let z = x + y; // もう一度 x が使える
C++ ではデフォルトでオブジェクトがコピーされる Person manager = developer; とか書くとオブジェクトがコピーされる (C++ では、というか、もともと C
では構造体の代入は memcpy と等価) これをコンストラクターという概念と両立させるには?
コピーコンストラクター • 自分と同じ型の const 参照だけを受け取るコンストラクター • (条件が揃えば) 暗黙的に定義される • 自分で定義することもできる
Person manager = developer; を Person manager(developer); に等しいコンストラクター呼び出しの一種として扱うようにした
コピーコンストラクターって非効率じゃね? • Const 参照を受け取るので、元のオブジェクトを変化させることはできない • 実質的には移動ではなく複製が行われる • std::string や std::vector
は複製時にヒープを確保する • 移動先オブジェクトのコンストラクターでヒープ確保した直後に 移動元オブジェクトのデストラクターでヒープ解放するの? 😒 Person(const Person &p) : name(p.name), age(p.age) {} std::string のコンストラクター内で ヒープが確保される
ムーブコンストラクター • 自分と同じ型の右辺値参照のみを受け取るコンストラクター • (条件が揃えば) 暗黙的に定義される • Const でないので引数を書き換えることができる ◦
元のオブジェクトが管理するヒープ領域を奪って自分のものにできる ◦ 元のオブジェクト自体がなくなるわけではない ▪ 元のオブジェクトのデストラクターは後で普通に呼ばれる Person(Person &&p) : … { … } // ムーブコンストラクター Person(const Person &p) : … { … } // コピーコンストラクター
右辺値参照 ……ナニソレ?😅 • コピーとムーブ、二つのコンストラクターを区別するために追加された概念 • 普通の参照 (左辺値参照) と右辺値参照は別の型という扱い // ↓引数の型が異なるのでオーバーロードが許される
Person(Person &&p) : … { … } // 引数は右辺値参照 Person(const Person &p) : … { … } // 引数は左辺値参照
右辺値参照はキャストして作る // デフォルトは左辺値参照なのでコピーされる Person manager = developer; // 引数を右辺値参照にキャストして渡すとムーブされる Person
manager = static_cast<Person&&>(developer); // いちいちキャストするのがめんどいのでキャストするためだけのライブラリー関数がある Person manager = std::move(developer);
コピー & ムーブ Rust • 代入によって オブジェクトを生成・移動する • clone 関数を呼んで
オブジェクトを複製する • clone の実装は derive できる • 一部の型のオブジェクトは暗黙的に clone される ムーブがデフォルトで コピーしたいとき明示する C++ • コンストラクターを呼ぶことで オブジェクトを生成・移動・複製する • コピー/ムーブコンストラクターは 暗黙的に定義される場合がある • 引数の型で コピーとムーブを使い分ける ◦ 左辺値参照 vs 右辺値参照 コピーがデフォルトで ムーブしたいとき明示する ここまでのまとめ
すでに存在する変数への代入
Rust の代入は元のオブジェクトを解体する struct S(i32); impl Drop for S { fn
drop(&mut self) { println!("{}", self.0); } } fn main() { let mut a = S(0); a = S(1); // 代入時に S(0) のデストラクターが実行される // 関数の最後で S(1) のデストラクターが実行される }
C++ の代入は関数呼び出しの糖衣構文 std::string a("foo"); // 生成 std::string b = "bar";
// 生成 a = b; // 代入 a.operator=(b); // 代入の正体
コピー代入・ムーブ代入 • 代入はデフォルトではコピーを行う ◦ もともと C では構造体の代入は memcpy と等価 •
代入時もコンストラクターと同様にコピーとムーブを区別したい • (条件が揃えば) コピー代入演算子が暗黙的に定義される • (条件が揃えば) ムーブ代入演算子が暗黙的に定義される • 自分で定義することもできる
すでに存在する変数への代入 Rust • 古いオブジェクトは解体される • 他は変数の定義と再代入は同じ C++ • 再代入は関数呼び出しの糖衣構文 ◦
コンストラクターと同じく コピーとムーブの区別がある • 再代入自体は オブジェクトの生成も解体もしない ここまでのまとめ
関数呼び出しでの値のやりとり
C++ は生成によって移動する • 引数を値渡しするとき、オブジェクトの “移動” が発生 • 戻り値を返すとき、オブジェクトの “移動” が発生
• 毎回、移動先を生成し移動元を削除するのか? 😟 struct S {}; S f() { S s = S(); return s; } int main(void) { S s = S(f()); }
Copy elision • オブジェクトを移動せずに済ませる最適化 ◦ 移動先が予め分かっている場合 ◦ 移動元オブジェクトを最初から移動先のアドレスに生成する ◦ コピー
or ムーブコンストラクターの呼び出しが不要になる ▪ ただしコンストラクター内に printf とかがあると動作が変わる • 実際に最適化されるかどうかは種々の条件により異なる ◦ 関数呼び出しや return の式の書き方 ◦ C++ のバージョン ◦ コンパイラーに指定した最適化オプション ◦ コンパイラーの賢さ
関数の引数・戻り値の受け渡し Rust • 代入としてコンパイラーが最適化する C++ • Copy elision で最適化されたり されなかったりする
ここまでのまとめ
コンテナーの要素の出し入れ
コンテナーに効率よくオブジェクトを挿入したい std::vector<std::pair<int, int>> v; v.push_back({1, 2}); // 先に pair が生成されてから
vector 内へと移動される // 最初から vector 内に pair を生成するには?
コンストラクターを vector の中で呼ぶしかない std::vector<std::pair<int, int>> v; v.emplace_back(1, 2); // emplace_back
の中で直接 pair のコンストラクターが呼ばれる // オブジェクトを移動する必要がなくなった!
あらゆる引数をコンストラクターに渡せるためには ようこそテンプレートの世界へ template<typename T> class vector { template<typename... Args> T&
emplace_back(Args&&... args) { /*略*/ } }; テンプレの型に && が付いてるが 左辺値参照を渡すこともできる 可変長引数
コンテナーの要素の出し入れ Rust • オブジェクトを先に生成してから コンテナー内に移動する C++ • コンテナー内に直接オブジェクトを 生成できる •
コンストラクターに引数を渡すために テンプレートを駆使する ここまでのまとめ
まとめ • Rust のオブジェクトは代入により移動できる ◦ 細かいことはコンパイラーにおまかせ • C++ のオブジェクトは直接移動できない ◦
移動するためにコンストラクターや代入演算子を使う ◦ 移動を軽くするためにムーブコンストラクターやムーブ代入演算子を使う ◦ 移動を省くために copy elision やテンプレートを駆使する