C ++のデフォルトの参照渡しセマンティクス[終了]
-
02-07-2019 - |
質問
編集:この質問は、C ++自体よりも言語工学に関するものです。私はC ++を例として使用して、欲しいものを示しました。これは主に毎日使用しているためです。 C ++での動作を知りたくありませんでしたが、どうすれば 実行できるかについての議論を開きました。
それは現在の動作方法ではなく、それが私がそれを行うことを望む方法であり、それは確かにCの互換性を壊すでしょうが、それはextern" C"だと思いますすべてです。
つまり、現在宣言しているすべての関数またはメソッドでは、参照演算子の前に参照演算子を付けてオブジェクトを送信することを明示的に記述する必要があります。実際、32ビットを超えるサイズのすべてのオブジェクトに使用するため、POD以外のすべてのタイプが参照によって自動的に送信されることを望みます。これはほとんどすべてのクラスです。
今の状態を例に挙げましょう。 a 、 b 、 c をクラスと仮定します:
class example { public: int just_use_a(const a &object); int use_and_mess_with_b(b &object); void do_nothing_on_c(c object); };
今、私が望むもの:
class example { public: int just_use_a(const a object); int use_and_mess_with_b(b object); extern "C" void do_nothing_on_c(c object); };
今、do_nothing_on_c()は今日のように動作する可能性があります。
それは少なくとも私にとっては興味深いものであり、より明確に感じます。また、POD以外のすべてのパラメーターが参照によって来ている場合、知っている場合、明示的に宣言します。
この変更の別の視点は、Cから来た人から、参照演算子は変数アドレスを取得する方法のように思えます。これが私がポインタを取得するために使用した方法です。つまり、これは同じ演算子ですが、異なるコンテキストで異なるセマンティックを使用しているので、あなたにとっても少し違和感を感じませんか?
解決
C ++の要点とC ++のセマンティクスが欠けていると思います。あなたは C ++が値で(ほとんど)すべてを渡すことで正しいという事実を見逃しました。それはCで行われている方法だからです。常に。ただし、Cだけでなく、以下に示すように...
Cのパラメーターセマンティクス
Cでは、すべてが値で渡されます。 「プリミティブ」および「POD」値をコピーして渡されます。関数内でそれらを変更すると、オリジナルは変更されません。それでも、一部のPODをコピーするコストは些細なことではありません。
ポインター表記(*)を使用する場合、参照渡しではありません。住所のコピーを渡します。多少の違いはありますが、ほぼ同じです:
typedef struct { int value ; } P ;
/* p is a pointer to P */
void doSomethingElse(P * p)
{
p->value = 32 ;
p = malloc(sizeof(P)) ; /* Don't bother with the leak */
p->value = 45 ;
}
void doSomething()
{
P * p = malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
/* Value of p ? 25 ? 32 ? 42 ? */
}
p-> valueの最終値は32です。アドレスの値をコピーしてpが渡されたためです。したがって、元のpは変更されませんでした(そして、新しいpはリークされました)。
JavaおよびC Sharpのパラメーターセマンティクス
一部の人にとっては驚くかもしれませんが、Javaではすべてが値によってコピーされます。上記のCの例では、Javaでもまったく同じ結果が得られます。これはほとんどあなたが望むものですが、「参照/ポインタによって」プリミティブを渡すことはできません。 Cと同じくらい簡単に。
C#では、「ref」を追加しました;キーワード。これは、C ++のリファレンスとほぼ同様に機能します。ポイントは、C#では、関数宣言と、すべての呼び出しの両方で言及する必要があるということです。これもあなたが望むものではないでしょう。
C ++のパラメーターセマンティクス
C ++では、値をコピーすることでほとんどすべてが渡されます。シンボルのタイプのみを使用している場合、シンボルをコピーしています(Cで行われているように)。これが、*を使用しているときに、シンボルのアドレスのコピーを渡す理由です。
ただし、&を使用している場合は、実際のオブジェクト(struct、int、pointerなど)を渡すと仮定します。参照。
シンタックスシュガーと間違えるのは簡単です(つまり、舞台裏ではポインターのように機能し、生成されたコードはポインターに使用されるものと同じです)。しかし...
真実は、参照が構文的な砂糖以上のものであることです。
- ポインタとは異なり、スタック上にあるかのようにオブジェクトを操作します。
- unlineポインターは、constキーワードと関連付けられている場合、あるタイプから別のタイプへの暗黙の昇格を許可します(主にコンストラクターを介して)。
- ポインターとは異なり、シンボルはNULL /無効であることは想定されていません。
- 「コピーによる」とは異なり、オブジェクトのコピーに無駄な時間を費やしていません
- 「コピーによる」とは異なり、[out]パラメーターとして使用できます
- 「コピーによる」とは異なり、C ++でOOPの全範囲を使用できます(つまり、インターフェイスを待機している関数に完全なオブジェクトを渡します)。
つまり、リファレンスは両方の世界で最高のものです。
Cの例を見てみましょうが、doSomethingElse関数にC ++のバリエーションを追加します。
struct P { int value ; } ;
// p is a reference to a pointer to P
void doSomethingElse(P * & p)
{
p->value = 32 ;
p = (P *) malloc(sizeof(P)) ; // Don't bother with the leak
p->value = 45 ;
}
void doSomething()
{
P * p = (P *) malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
// Value of p ? 25 ? 32 ? 42 ?
}
結果は42で、古いpがリークされ、新しいpに置き換えられました。 Cコードとは異なり、ポインターのコピーではなく、ポインターへの参照、つまりポインター自体を渡すからです。
C ++を使用する場合、上記の例は非常に明確でなければなりません。そうでない場合は、何かが欠落しています。
結論
C ++は、C、C#、またはJava(JavaScript ... :-p ...)でも、すべてが機能する方法であるため、コピー/値渡しです。また、C#と同様に、C ++にはボーナスとして参照演算子/キーワードがあります。
今、私が理解している限り、あなたはおそらく私が半ば冗談で言っていることを行っているでしょう C + 、つまり、C ++の機能が制限されたC。
おそらくあなたのソリューションはtypedefを使用しているでしょう(しかし、C ++の同僚に、役に立たないtypedefによって汚染されたコードを見るように怒らせます...)が、これを行うと、実際にC ++が欠落しているという事実がわかりにくくなります。
別の投稿で述べたように、あなたはあなたの
他のヒント
コードのセクションの意味を完全に変更するコンパイラオプションは、私には本当に悪い考えのように聞こえます。 C ++構文を使用するか、別の言語を見つけてください。
すべての(修飾されていない)パラメーターを参照にすることで、参照を悪用しないようにします。
参照がC ++に追加された主な理由は、 toでした。演算子のオーバーロードをサポート; 「参照渡し」が必要な場合セマンティクス、Cにはそれを行うための完全に合理的な方法がありました:ポインター。
ポインタを使用すると、先のとがったオブジェクトの値を変更する意図が明確になります。関数呼び出しを見るだけでこれを確認できます。関数宣言を見て、それが使用されているかどうかを確認する必要はありません。参照。
また、参照
引数を変更したいのですが、 ポインタを使用する必要がありますか 参照?強いのか分からない 論理的な理由。渡さない場合 オブジェクト ''(たとえば、nullポインター)は 許容できる、ポインタを使用すると センス。私の個人的なスタイルは 変更したいときのポインタ いくつかのコンテキストでは それを見つけやすくします 変更が可能です。
はい、それはかなり混乱したオーバーロードだと思います。
これは、マイクロソフトがこの状況について言わなければならないことです。
参照宣言とアドレス演算子の使用を混同しないでください。いつ&識別子の前にintやcharなどの型があり、識別子は型への参照として宣言されます。いつ&識別子の前に型はありません。使用法はアドレス演算子です。
私はCやC ++にはあまり向いていませんが、*と&のさまざまな使用法を整理するという大きな頭痛がします。アセンブラーでコーディングするよりも両方の言語で。
最善のアドバイスは、あなたが本当に起こりたいことについて考える習慣をつけることです。参照渡しは、コピーコンストラクターがない(または使用しない)場合に便利であり、大きなオブジェクトの場合は安価です。ただし、その場合、パラメーターの変更はクラス外で感じられます。代わりに const
参照で渡すこともできます。その場合、変更はありませんが、ローカルで変更することはできません。関数内で読み取り専用である必要のある安価なオブジェクトにはconst by-valueを渡し、ローカルで変更できるコピーが必要な場合は非const by-valueを渡します。
各順列(値ごと/参照ごと、const /非定数ごと)には重要な違いがありますが、これらは明らかに同等ではありません。
値を渡すと、データがスタックにコピーされます。渡す構造体またはクラスに対してoperator =が定義されている場合、実行されます。提案された変更が本質的に引き起こす暗黙の言語の混乱の不正確さを洗い流すであろうと私が知っているコンパイラ指令はありません。
一般的なベストプラクティスは、値を参照だけでなくconst参照で渡すことです。これにより、呼び出し元の関数で値を変更できなくなります。これは、const-correctコードベースの1つの要素です。
完全に正しいconstのコードベースはさらに先へ進み、プロトタイプの最後にconstを追加します。考慮:
void Foo::PrintStats( void ) const {
/* Cannot modify Foo member variables */
}
void Foo::ChangeStats( void ) {
/* Can modify foo member variables */
}
constという接頭辞が付いた関数にFooオブジェクトを渡す場合、PrintStats()を呼び出すことができます。コンパイラは、ChangeStats()の呼び出しでエラーになります。
void ManipulateFoo( const Foo &foo )
{
foo.PrintStats(); // Works
foo.ChangeStats(); // Oops; compile error
}
C ++での値渡し/参照渡しのアイデア全体が誤解を招くと正直に思います。 すべては値渡しです。次の3つのケースがあります。
-
変数のローカルコピーを渡す場所
void myFunct(int cantChangeMyValue)
-
変数へのポインターのローカルコピーを渡す場所
void myFunct(int* cantChangeMyAddress) { *cantChangeMyAddress = 10; }
-
「参照」を渡す場所ですが、コンパイラマジックを通じて、まるでポインタを渡し、毎回単に参照解除するかのようになります。
void myFunct(int & hereBeMagic) { hereBeMagic = 10; // same as 2, without the dereference }
個人的には、すべてが値渡しであるということを覚えておくと、混乱が少なくなります。場合によっては、その値はアドレスである可能性があります。これにより、関数の外で物事を変更できます。
あなたが提案していることは、プログラマーが1番をすることを許しません。私は個人的に、そのオプションを取り去ることは悪い考えだと思います。 C / C ++の大きな利点の1つは、きめ細かいメモリ管理を備えていることです。すべてを参照渡しにすることは、単にC ++をJavaのようにしようとすることです。
明確でないものがあります。あなたが言うとき:
int b(b& param);
2番目の 'b'の意図は何ですか?型を導入するのを忘れましたか?最初の 'b'に関して別の書き方をするのを忘れましたか?次のように書くのは明らかだと思いませんか:
class B{/*something...*/};
int b(B& param);
今から、あなたが私が書いたものを意味すると思います。
今、あなたの質問は「コンパイラが非PODのすべての値渡しをref-by-refとみなす方が良いと思いませんか?」です。 最初の問題は、契約が破られることです。参照だけでなく、CONST参照によるパスを意味すると思います。
今、あなたの質問はこれになりました:"関数呼び出しを値で最適化できるコンパイラ指令があるかどうか知っていますか?"
今の答えは「わからない」です。
利用可能なすべての種類のパラメーターとそのconstバリエーションを混在させると、c ++は非常に乱雑になると思います。
すべてのコピーコンストラクター呼び出し、オーバーロードされたすべての逆参照などを追跡するには、すぐに手に負えなくなります。