結局C++とRustってどっちが良いの?
■ このスレッドは過去ログ倉庫に格納されています
C++の色々配慮してめんどくさい感じは好きだけど、実務になったらメモリ安全性とか考えて今後Rustに変わっていくんかな? >>678
これってダウンキャスト入ってるから危険なこともできそう
相当するコードはRustではコンパイル通らない?
#include <iostream>
#define DOUT(arg) std::cout << #arg": " << arg << std::endl
template<typename T> struct AddN {
void add2() {
static_cast<T*>(this)->add1();
static_cast<T*>(this)->add1();
}
};
struct Foo : AddN<Foo> {
int x;
Foo(int i) : x(i) {}
void add1() {
this->x++;
}
};
struct Bar : AddN<Foo> {
double y;
Bar(double i) : y(i) {}
void add1() {
this->y+=0.1;
}
};
int main () {
Foo foo (1); Bar bar (0.1);
DOUT (foo.x); DOUT (bar.y);
foo.add1 (); bar.add1 ();
DOUT (foo.x); DOUT (bar.y);
return 0;
} 実行したら以下のようになった
$ ./a.out
foo.x: 1
bar.y: 0.1
foo.x: 2
bar.y: 0.2
最後のbar.yは1.1になると思ってたんだがはて? そもそもBarはFooを継承してないんだから
ダウンキャストのところでコンパイルエラー出て良いようなもんだけど
何で通すんだろ? templateだとチェックを緩和してるのかな?
あるいはコンパイラのバグかな?
$ g++ --version
g++ (Debian 10.2.1-6) 10.2.1 20210110 -ダウンキャストのところでコンパイルエラー出て良いようなもんだけど
+static_cast<T*>(this)->add1();のところでコンパイルエラー出て良いようなもんだけど 最近のC++はよく分からんけどその例だとadd2使わないと多分変なことにならない
doubleをint扱いして1足しても1/(2^53)(←不正確、指数部による)くらいしか変動しないのかな
FooもBarもAddN継承してるからstatic_castでコンパイルエラーは出ないはず
Rustはクラス継承がないからstatic_castとの正確な比較はできない
traitで似たようなコード書くときはSelfキーワードで自分の型を指すからダウンキャスト必要ないし
敢えてstatic_castと対応させるならtransmuteだろうけどunsafe付きの関数だから危険でもコンパイルは通せる
(unsafeを使う範囲の安全性保証はプログラマの責任) >>703
>最近のC++はよく分からんけどその例だとadd2使わないと多分変なことにならない
あーごめんごめん!興味があったのはadd2を呼んだときの挙動だった
- foo.add1 (); bar.add1 ();
+ foo.add2 (); bar.add2 ();
$ ./test
foo.x: 1
bar.y: 0.1
foo.x: 3
bar.y: 0.1 <- 鼻から悪魔
>FooもBarもAddN継承してるからstatic_castでコンパイルエラーは出ないはず
なるほどキャストしているthisはAddN<Foo>*だから
Foo*にもBar*にもダウンキャストできるってことね
ありがとう 正当?なC++的にはdynamic_castすべきなんじゃなかろうかって思うんだよね
でそれはvtable介してadd1にアクセスするのと変わらない
プログラマの責任でstatic_castして効率化する
ってテクニックは俺は割と好きだけども >>703
transmuteはダウンキャストではなく任意の型同士の強引な型変換(読み替え)だから当然unsafe
一方でダウンキャストは親から子、基底から派生、抽象から具体への変換を指す
Rustの場合は抽象的なトレイトから具体的な型への変換がダウンキャストとなる
Rustは基本的には静的ポリモーフィズムで
コンパイル時点で各具体的な型のコードへ安全に置き換わるモノモーフィズムなので
C++のstatic_castのような危険のあるダウンキャストを必要としないが正解
Rustでもdyn Traitを用いた場合のみ動的ポリモーフィズムとなり
C++のdynamic_castと同様に実行時ダウンキャストがdowncast()/downcast_ref()/downcast_mut()で可能だが
C++とは異なりRustはNull安全でダウンキャストできる そこは、わずかにコストを支払って、reinterpret_castでもいいとは思うけどね ああちがう、reinterpret_castを避ける、だ 動的検査のコストよりもキャストに失敗した時の分岐処理のせいで最適化が阻害されるのを問題視してる感じ
かといって分岐しないなら動的検査の意味ないし >>706
ダウンキャストの根本的な問題点を理解してないね
静的か動的かUBかどうかは副次的な問題点に過ぎない Rustのポインタ(参照)のNull安全ってすごく上手い仕組みだな
C++のdynamic_castもRustのdowncast_refも生成コードはどちらもダウンキャスト成功時はそのままポインタを返して失敗時は0を返す点で効率も同じだけど
Rustでは返す型としてはそれをポインタで直接に表さずにOption<&T>を返すと表現させて
成功時はOption::Some(&T)を返して失敗時の0はOption::Noneとして返すため
プログラムコード上はNoneかSomeか生成コードでは0か否かをチェックせずには使えないわけだ
C++でのNullポインタか否かのチェック忘れを静的な型チェックで防止できるってことは
もしかしてC++でもdynamic_castやその他のNullポインタを返すライブラリ全てをstd::optionalを返すように変更すればNull安全になるんじゃないか? >>710はいつものデタラメ言いがかり君に似てる
闇雲に否定して別の問題点があると主張しつつ
その問題点を述べることは一切ないので書き込みの中身がない >>711
デフォルトmoveの導入は無理だと思ってるくせにNull安全は導入できると思ってるのかw
オツム弱っww たぶん人違いじゃないかな?
dynamic_castの返り値を確認しないやつは
流石におらんと思うよ
静的に確認してくれても全く嬉しくない ヌルポインタが返る全ての関数についてそうしないとヌル安全にならない
ヌルを使ってはダメ それは当然だよ
C++では指し示した先にインスタンスがあるかどうか
分からんときにのみポインタを使う
確実にあるときは参照を使うのが流儀
ポインタが使われるところではnullptrのチェックを行う
この流儀の部分を守らないスカタンが問題なんだな
Cに参照がないので上記流儀が守られないことも問題 >>704
なぜそのC++のプログラムは正しく動かいていないの?
bar.yはadd2()で0.2足されて0.1から0.3にならないといけないのに0.1のままになってる
>>699のソースコードを見ても正しく動かない原因がよくわからない >>718
BarはAddN<Bar>ではなくAddN<Foo>を継承しているから >>719
add2()としてBarの+0.2が使われずFooの+2か使われるということ?
bar.yが2.1になっていないのはなぜ? templateを展開すると
void Add <Foo>::add2() {
static_cast<Foo*>(this)->add1();
static_cast<Foo*>(this)->add1();
}
thisはBarの基底AddN<Foo>*のポインタ
これをBarと無関係のFoo*にキャストしたら動作は不定 なるほど
浮動小数点に対してそれを整数と見て+2してしまっているのかな
本来はコンパイル段階でエラーにしないとやばそうだ
今回はっきり動いていないとわかるケースだからいいけど何となく動いてしまってるケースがあると恐ろしい 正当?なC++的にはdynamic_castすべきだと思うんだよね
template<typename T> struct AddN {
void add2() {
dynamic_cast<T&>(*this).add1();
dynamic_cast<T&>(*this).add1();
}
virtual ~AddN () {};
}; C++最適化効きすぎてadd2そのものが展開されて最短コードにしかならん doubleのビット列をintとして扱ってインクリメントしてるから結果がおかしいんだろ
なぜコンパイラは型不一致エラーを出せないんだ? 問題があれば何でもコンパイル時点でエラーを出してくれるRustを使おう
実行デバッグが激減して開発効率も高い ライブラリが一個もない状態から基本的なライブラリを作るためにunsafeが必要という話だったら
有限個のバイナリファイルが正しく出力されればいいだけなので
ソースコードをチェックしなくても出力をチェックすればいいのでは? そういえばJavaが全然話題にもならないが言語のヒエラルキーって
C++ 最強カミソリ
剃り残しナシ
Rust 安全カミソリ
キレテナーィ なまくら
C# イケてるが
GCがあるからダメぽ
Java 論外
ヌルポとGCがあるからダメぽ
こんな感じ? 一つ間違うと自分の手とか余計なところまで切れちゃうw 俺にとってのC++は、日本語だよ 物心ついたときには、C++だったんだ
氏ぬまで忘れないと思う だからある意味最強、そんな奴が結構多いんだと思う
「ちゃんと話せ」って躾けられたのも、良し悪し >>733
逆だろ
RustはC++ができることは何でもできる
C++はできないことが多すぎ >>737
C++はできないことが多すぎる
このスレに出てきた話だけでも
代数的データ型
パターンマッチング
各種null安全
データ競合安全
メモリ安全
など多数 プリミティブはいろいろ備わってるから、やってできなくはないんだろうけど、
強制する方法がないから、ちっとも普及しないんだよね >>740
C++はそのプリミティブを多数欠いている
まさか代数的データ型をunionで頑張ればできると主張するのか?
パターンマッチングは構文だからC++には無理
データ競合を静的に検知する方法もない C/C++には昔から、「なければgenerateすればいいじゃん」っていう文化がある
パターンマッチングは、いまどきIDEが記述支援するんだから、やってできなくはない
データ競合やらは、「そういう」スマポを導入すればいい
でもね、みんな使わないんだわ 使われないものは、ないものとして詰られても仕方ない
あ、Cとおんなじように、入れ子になった構造体をささっと書きたいとかは思うね
もうできるようになったっけ? …いやまてよ、パターンマッチングって、エラー等検出のことだと思ってたけど、
パターンマッチング Rust でぐぐったら全然違うものが出てきたぜ その節は撤回 ちょっと読んでみる std::visitはパターンマッチに含まれますか パターンマッチングはC++23に入れようとしたがRustと比べて機能も弱すぎて失敗している
Rustのパターンマッチングはこんな感じで記述性や可読性を向上させている
fn slice_pattern(slice: &[(i32, i32)]) {
match slice {
[] => println!("空です"),
[(a, b)] => println!("要素は1つで({a},{b})です"),
[_, (123, b), ..] => println!("2つ目の前者が123なものは(123,{b})です"),
[.., next_last, _] => println!("その他の最後から2つ目の要素は{next_last:?}です"),
}
}
fn enum_pattern(shape: Shape) {
match shape {
Shape::Circle(r) => println!("半径{r}の円です"),
Shape::Rectangle(w, h) if w == h => println!("長さ{w}の正方形です"),
Shape::Rectangle(w, h) => println!("幅{w}高さ{h}の長方形です"),
x => println!("その他の図形{x:?}です"),
}
}
fn struct_pattern() {
let a = Foo { bar: 123, baz: 456, qux: 789 };
let b = Foo { baz: 555, ..a };
for Foo { bar, baz, qux } in &[a, b] {
println!("Foo: bar={bar}, baz={baz}, qux={qux}");
}
}
fn range_pattern(c: char) {
match c {
'0'..='9' => println!("数字({c})です"),
'a'..='z' | 'A'..='Z' => println!("アルファベット({c})です"),
_ => println!("その他の文字({c})です"),
}
} C++はユーザが多いので標準でなくてもライブラリがあるからね
Rustはユーザが少ないので標準で用意しとく必要がある
パタンマッチングは言語の話だけども Rust の TcpListener のサンプルについて質問です
1ページ目は無料で1ページ目だけで動作するはずですが
https://xtech.nikkei.com/atcl/nxt/column/18/01920/081600022/
何故か1byteしか受け取らず常に 404 NotFound になります
何処を治せばよいですか? >>747
C++のstd::variantは全体の型名を命名できない
各要素に対して専用の型を用意しなければならない
扱いづらいなど欠陥品
例えば>>748のShapeの定義例は
enum Shape {
Circle(u32),
Rectangle(u32, u32),
Parallelogram(u32, u32),
}
これだけで済む
さらに型Shapeに対して様々なメソッドを実装できる
C++ではそれぞれ困難と不可能
>>749
言語がサポートしないとライブラリでは無理 一発目から GET / HTTP/1.1 じゃないものが来てるかもよ、buffer 表示させてみては >>753
読んだけどそれでは無理
こちらはパターンマッチングのRustのコード例を>>748に出した
C++でも可能だと主張するならばそれぞれの実現コードをまず出すべき パターンマッチングの件、5分くらい読んできた
そういう風に書きたいっていうニーズがあるんだな、表現力を誇るC++でこれが書けないのは確かに手落ちw
この型はあれでもこれでもあるっていうの、あんまり扱ってこなかったけど、
上手く書けば便利になるかもね ただし、ゼロサンクでね >>750
そのRustのTcpListenerのサンプル動かしてみたけど普通に動いた
返すindex.htmlを用意してcargo run
ブラウザからhttp://localhost:9999/で表示された >>754
「無理」とだけ言われても何も分からないので具体的にお願いします
それとも記事を読んだうえでも、無理な点の指摘は>>751ですべてだということですか
例を読んでいれば「各要素に対して専用の型を用意しなければならない」は嘘だとすぐ分かるはずですが >>755
Rustは>>748の各パターンマッチング例をオーバーヘッド無しで処理してくれる
使わずに手動でダラダラと書ける分もパターンマッチング記述の方が最適化が良いかもしれん >>757
具体的に>>748にRustのパターンマッチングの各例のコードを書きました
次はC++でも可能だと主張するあなたが対応するC++のコードを出す番です >>759
あなたの要求に答えるために質問だけさせてください
明確にしてほしいのは「何が無理なのか」の「何」の部分です
よろしくおねがいします 教科書的サンプルとは別に、実践的なサンプルが見たいぞ
必要だから入った仕様だろう、手ごろにどっかあるはずだ お勧めのやつをたのむ >>751
>>749>パタンマッチングは言語の話だけども
>例えば>>748のShapeの定義例は
>enum Shape {
> Circle(u32),
> Rectangle(u32, u32),
> Parallelogram(u32, u32),
>}
>これだけで済む
>さらに型Shapeに対して様々なメソッドを実装できる
>C++ではそれぞれ困難と不可能
横レスだけども
namespace Shape {
struct Circle {uint32_t r;};
struct Rectangle {uint32_t w; uint32_t h;};
struct Parallelogram {uint32_t ui0; uint32_t ui1;};
template <typename T> void function (ostream &os, const T &p);
void function (ostream &os, const Circle &p);
void function (ostream &os, const Rectangle &p);
// void function (ostream &os, const Parallelogram &p);
} >>762
それだとShapeが型ではなく名前空間になってしまってるからダメでしょ >>763
namespaceをstructに置換して最後に;つければ? >>764
それだと全ての図形で必要となるメモリサイズの合計サイズが必要になっちゃうよ >>765
メンバ型定義しただけではメンバ変数は増えないから、サイズも増えないよ >>763
>それだとShapeが型ではなく名前空間になってしまってるからダメでしょ
何で? >>760
Rustはパターンマッチングがあるため>>748のようにシンプルに記述ができて可読性にも優れている
あなたはC++でも代わりの手段で表現することが可能だと主張した
それが無理ではないことをRustコードの各々に対応する具体的なC++のコードとして示してほしい パターンマッチングは便利だけども必須じゃないよね
所有権システムと違って後方互換性が犠牲になることはないので
そのうちC++に入るよ >>769
パターンマッチング>>748の2番目の例の話だからShapeは関数に渡ってくる型じゃないとまずい
単体で動くコードで比較すればはっきりすると思うのでC++版を書いて。以下はRust版
// ここは再掲
fn enum_pattern(shape: Shape) {
match shape {
Shape::Circle(r) => println!("半径{r}の円です"),
Shape::Rectangle(w, h) if w == h => println!("長さ{w}の正方形です"),
Shape::Rectangle(w, h) => println!("幅{w}高さ{h}の長方形です"),
x => println!("その他の図形{x:?}です"),
}
}
#[derive(Debug)]
enum Shape {
Circle(u32),
Rectangle(u32, u32),
Parallelogram(u32, u32),
}
fn main() {
enum_pattern(Shape::Circle(100));
enum_pattern(Shape::Rectangle(200, 300));
enum_pattern(Shape::Rectangle(567, 567));
enum_pattern(Shape::Parallelogram(789, 456));
}
// 実行結果
半径100の円です
幅200高さ300の長方形です
長さ567の正方形です
その他の図形Parallelogram(789, 456)です >>772
>パターンマッチング>>748の2番目の例の話だからShapeは関数に渡ってくる型じゃないとまずい
何で? ちなみにディスパッチは>>762は静的に行われる
>>772は... >>773
Shape型が渡ってきてその処理をしてるからだよね
とりあえずC++の動くコードを出してみたら? >>775
Shape型を引数で取る必要があるならC++では
Shapeを基底として継承させるよ
もちろん静的ディスパッチはできない 図形は典型的なオブジェクト指向の例題だから
enumを使う例としては適切ではないんじゃないかな?
Rustをよく知らん俺からしたらピンとこないよ >>774
ディスパッチは静的には不可能で動的にしか行われないでしょ
静的に可能なのはモノモーフィゼイションだけど今回の例では適用できませんね
いずれにしてもC++で可能だと言うコードを示してみたら? >>778
>ディスパッチは静的には不可能で動的にしか行われないでしょ
いいえ>>762は静的に行われる
>いずれにしてもC++で可能だと言うコードを示してみたら?
>>762に示した >>780
動くコードじゃないと意味がないのでmain()付きの動くコードを示そうよ
Rust版は>>772
>>781
パターンマッチングを全く行なっていないじゃん
例えば分かりやすくこの項目を増やすとして
Shape::Rectangle(100, h) => println!("幅100で高さ{h}の長方形です"),
これはC++でif (w == 100)のコードになるわけだよね
それはパターンマッチングとは言わないよ >>782
enumのバリアント判定に相当するパターンマッチを行っていますので、全く行っていないという指摘は正しくありません
また、>>748のenum_patternにShape::Rectangle(100, h)にマッチするコードは含まれておらず、あなたの当初の要求に含まれていないものです
新しい要求を後から追加して批判するのは、誠実な態度とは言えません
このようなことが無いように、「何が無理か」を明確にするよう確認したつもりでした
今後はこうしたことが無いように、事前よく確認することをお願い申し上げます
また値によるマッチについてですが、同様の考えで似たライブラリを実装された方を見つけました
こちらは値によるマッチに拡張したライブラリを実装されているようです
https://qiita.com/Naotonosato/items/a1e710de2b78346146d1 >>781
かなり苦しいね
>>748の残り3つのパターンマッチングには応用できそうもないけど書ける? >>782
別に関数プロトタイプまで書いてるんだから分かりそうなものだけど...
以下は静的ディスパッチでコンパイル時に定まるよ
>>772が静的ディスパッチできないとしても
たぶん同じように書けばRustでも静的にディスパッチできると思うよ
#include <iostream>
using namespace std;
namespace Shape {
struct Circle {uint32_t r;};
struct Rectangle {uint32_t w; uint32_t h;};
struct Parallelogram {uint32_t ui0; uint32_t ui1;};
template <typename T> void function (ostream &os, const T &p) {}
void function (ostream &os, const Circle &p) {}
void function (ostream &os, const Rectangle &p) {}
}
int main () {
using namespace Shape;
Circle circle;
function (cout, circle);
Parallelogram parallelogram;
function (cout, parallelogram);
return 0;
} 静的ではなくて動的ディスパッチだろ
このように次々と何が来るかわからないリストが渡ってきた場合
Shape::Shape shape_list[4];
shape_list[0] = Shape::Rectangle{w: 33, h: 33};
shape_list[1] = Shape::Parallelogram{ui0: 4, ui1: 33};
shape_list[2] = Shape::Rectangle{w: 33, h: 4};
shape_list[3] = Shape::Circle{10};
その時にこれで処理できるのだから動的ディスパッチをしている
for (int i = 0; i < 4; i++) {
enum_pattern(shape_list[i]);
} >>786
静的ディスパッチと書いているのは俺が書いた>>762と>>785のC++の例
コンパイル時にどの関数が呼ばれるか型によって決まる もうちょっと調べてたが、C++にもinspectってのが来そうみたいじゃん
パタンマッチングって、こんなもんが流行ってるのね、また一つ取り残されてたぜ >>786
いいえ、これは静的ディスパッチです
簡単に確認する方法として、生成されたアセンブリ中のvtableを確認する方法があります:
https://godbolt.org/z/1W7jGnWEd
"vtable for std::bad_variant_access:"が唯一のvtableであり、Circleなどのためのvtableはありません
このことから、動的ディスパッチは発生しないことが分かります >>789
おいおい
vtable使うことだけが動的ディスパッチだと思いこんでいるのか?
実行時にデータ内容に応じて分岐することを動的ディスパッチと言う
>>786はもちろん動的ディスパッチをしている
次に何が来るかはコンパイル時点で決まらないため静的に決定は不可能だ >>788
昔はなかなか規格としてまとまらなかったが最近のC++はどうした?
俺もboostに入ってたやつくらいしか追えていない >>790
それはRsutのコードか? C++のコードか? >>793
>>785は静的ディスパッチだよ
コンパイル時にどの関数が呼ばれるか定まる >>790
いいえ
動的ディスパッチとは、多態メソッドの呼び出し式を実行するときに、具体型に応じて実際に呼ばれる関数が振り分けられることを言います
https://en.wikipedia.org/wiki/Dynamic_dispatch
動的ディスパッチはしばしばパフォーマンスの低下をもたらすと言われますが、その最大の理由は、
選択される各関数ポインタを一度メモリから(vtableから)読み出し、それをcallする必要があることです
動的ディスパッチをどう定義するかはさておき、vtableが無い>>789ではこのパフォーマンス低下の懸念が無いことが分かります
また、「実行時にデータ内容に応じて分岐することを動的ディスパッチと言う」という定義には明らかな問題があります
それは、この定義ではmatchやifなど通常の制御構造も動的ディスパッチに当てはまってしまうということです
これは、この定義が一般的な定義から大きく逸脱していることをよく象徴的に表わしています
少なくとも「データ内容」は「型」に置き換える必要があることが分かるでしょう >>789
それは動的ディスパッチだよ
C++のstd::visitはstd::variantのindex()の値で実行時に分岐してる
だから>>786のような実行時になるまで何が来るか不明な場合にも対応できる >>791
inspectは、godboltでclangのexperimentalを遊べるようになってた
型に対しては、もうちょっとまだみたい、error: expected expression って言われた
ラムダみたいに、みんなが欲しがるものはそれでもわりと早いんだよね
一応入れとくか…みたいのは、いつまでたっても入らないw
ところで、godboltに、-Wlifetime ってのがみえたけど…これってもしかして C++の仕様を変えようという時に国語辞典の変更を許さないのはタイパ最悪だな
C++の仕様変更を許さない、とすれば秒速で終わる >>796
いいえ、これは動的ディスパッチではありません
「実行時になるまで何が来るか不明な場合にも対応できる」ことは、それが動的ディスパッチであることの証明にはなりません
繰り返しになりますが、>>790の「実行時にデータ内容に応じて分岐することを動的ディスパッチと言う」という定義は、一般的な定義とはまったく異なります
>>790の定義を採用する限りにおいてはその推論は正しいですが、そのためにはまずこの定義の出典および正確性を確認する必要があります ■ このスレッドは過去ログ倉庫に格納されています