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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
WATANABE Yuki
February 02, 2022
Programming
100
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
480
鉄道シミュレーターで自動運転を実装した話
magicant
0
380
ドキュメント、書けてますか?
magicant
0
130
Other Decks in Programming
See All in Programming
テストを司るデーモンに会いに行く 〜隔離した仮想マシンでテストを通すまで〜
h1d3mun3
1
600
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
300
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
390
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
1
2.1k
Heart of Swift Concurrency
koher
0
1.1k
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
4
410
Webの地図
yosuke_furukawa
PRO
6
5k
Workers Cache を知る
syumai
0
320
ゲームコントローラやキーボードのファームウェアをSwiftで書く
kishikawakatsumi
1
270
手動確認はもう限界 〜XCUITestでCustom URL Schemeの遷移を起動種別ごとに自動テストする〜 / Testing Custom URL Schemes with XCUITest
otouto
0
370
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
仕様駆動開発による爆速プロダクト開発 / Bakusoku Spec Driven Development
kobakei
0
160
Featured
See All Featured
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
390
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.2k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
430
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
590
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
730
Joys of Absence: A Defence of Solitary Play
codingconduct
1
530
The Art of Programming - Codeland 2020
erikaheidi
57
14k
A Modern Web Designer's Workflow
chriscoyier
699
190k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
12k
KATA
mclloyd
PRO
35
16k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
900
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 やテンプレートを駆使する