7

編集: この質問は、C++ 自体よりも言語工学に関するものです。主に日常的に使用するため、C++ を例として使用して、必要なものを示しました。私はそれが C++ でどのように機能するか知りたくありませんでしたが、それがどのように行われるかについての議論を開きまし

それは現在の動作方法ではありません。それができればいいのですが、それは確かにCの互換性を壊しますが、それがextern "C"のすべてだと思います。

つまり、今宣言するすべての関数またはメソッドで、参照演算子を前に付けて参照することでオブジェクトが送信されることを明示的に記述する必要があります。私はすべての非 POD 型が参照によって自動的に送信されることを望みます。なぜなら、実際にはサイズが 32 ビットを超えるすべてのオブジェクトに対して、それを頻繁に使用するからです。それは私のほとんどすべてのクラスです。

ab、およびcがクラスであると仮定して、現在の状況を例証しましょう。

クラスの例 {
    公衆:
        int just_use_a(const a &object);
        int use_and_mess_with_b(b &object);
        void do_nothing_on_c(c オブジェクト);
};

今私が望むもの:

クラスの例 {
    公衆:
        int just_use_a(const a object);
        int use_and_mess_with_b(b オブジェクト);
        extern "C" void do_nothing_on_c(c オブジェクト);
};

これで、do_nothing_on_c() は現在と同じように動作する可能性があります。

それは少なくとも私にとっては興味深いことであり、はるかに明確に感じられます。また、 POD 以外のすべてのパラメーターが参照によって来ていることがわかっている場合は、明示的に宣言する必要がある場合と同じ間違いになると思います。

この変更の別の観点から、C 出身者からすると、参照演算子は変数addressを取得する方法のように思えます。これは、ポインターを取得するために使用した方法です。つまり、同じ演算子ですが、異なるコンテキストでは異なる意味を持つということは、あなたにとっても少し間違っていると感じませんか?

4

9 に答える 9

15

C++ と C++ セマンティクスの要点を見逃していると思います。C で行われている方法であるため、(ほとんど) すべてを値で渡すことで C++ が正しいという事実を見逃していました。しかし、以下に示すように、C だけではありません...

C のパラメーター セマンティクス

C では、すべてが値渡しされます。「プリミティブ」と「POD」は、値をコピーして渡されます。関数でそれらを変更しても、元のファイルは変更されません。それでも、一部の POD をコピーするコストは、取るに足らないものになる可能性があります。

ポインター表記 ( * ) を使用する場合、参照渡しではありません。アドレスのコピーを渡しています。これはほぼ同じですが、わずかな違いが 1 つあります。

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 で行われているように)。これが、* を使用しているときに、シンボルのアドレスのコピーを渡す理由です。

しかし、& を使用している場合は、実際のオブジェクト (構造体、int、ポインターなど) を渡していると仮定します。参照。

これをシンタックス シュガーと間違えがちです (つまり、舞台裏ではポインターのように機能し、生成されたコードはポインターと同じように使用されます)。しかし...

真実は、参照が構文糖衣以上のものであるということです。

  • ポインターとは異なり、スタック上にあるかのようにオブジェクトを操作できます。
  • const キーワードに関連付けられている場合、unline ポインターは、(主にコンストラクターを介して) ある型から別の型への暗黙的な昇格を許可します。
  • ポインターとは異なり、シンボルは NULL/無効であることは想定されていません。
  • 「by-copy」とは異なり、オブジェクトのコピーに無駄な時間を費やすことはありません
  • 「by-copy」とは異なり、[out]パラメータとして使用できます
  • 「by-copy」とは異なり、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 を使用しています (ただし、役に立たない typedef によってコードが汚染されているのを見て、C++ の同僚を激怒させるでしょう...)。

別の投稿で述べたように、C 開発 (何でも) から C++ 開発に考え方を変えるか、おそらく別の言語に移行する必要があります。ただし、C++ の機能を使用して C の方法でプログラミングを続けないでください。使用するイディオムの力を意識的に無視/難読化すると、最適ではないコードが生成されます。

注: また、プリミティブ以外のものをコピーして渡さないでください。関数を OO 容量からキャストしますが、C++ では、これは望んでいるものではありません。

編集

質問は多少変更されました ( https://stackoverflow.com/revisions/146271/listを参照)。元の回答をそのままにして、以下の新しい質問に回答します。

C++ のデフォルトの参照渡しセマンティクスについてどう思いますか? あなたが言ったように、それは互換性を壊し、プリミティブ(つまり、コピーによって渡される組み込み型)と構造体/オブジェクト(参照として渡される)に対して異なるパスバイを持つことになります。「値渡し」を意味する別の演算子を追加する必要があります(extern「C」は非常にひどいもので、すでにまったく別の何かに使用されています)。いいえ、今日の C++ での方法がとても気に入っています。

[...]参照演算子は、変数アドレスを取得する方法のように思えます。これは、ポインターを取得するために使用した方法です。つまり、同じ演算子ですが、異なるコンテキストでは異なる意味を持つということは、あなたにとっても少し間違っていると感じませんか? はいといいえ。演算子 >> は、C++ ストリームで使用される場合にもセマンティクスを変更しました。次に、演算子 += を使用して strcat を置き換えることができます。演算子 & が使用されたのは、その意味が「ポインターの反対」であるためであり、さらに別の記号を使用したくなかったためだと思います (ASCII は制限されており、スコープ演算子 :: とポインター -> は、他の記号がほとんどないことを示しています使用可能です)。しかし今、もし & が気になるなら && は本当にあなたを不安にさせるでしょう、彼らは C++0x に単項 && を追加したからです (一種のスーパーリファレンス...)。自分で消化するのはまだ先ですが…

于 2008-09-28T18:41:07.163 に答える
10

コードのセクションの意味を完全に変更するコンパイラ オプションは、私には非常に悪い考えのように思えます。C++ 構文に慣れるか、別の言語を見つけてください。

于 2008-09-28T17:31:27.457 に答える
2

すべての(修飾されていない)パラメーターを参照にすることで、参照をこれ以上悪用したくありません。

参照が C++ に追加された主な理由は、演算子のオーバーロードをサポートするためでした。「参照渡し」のセマンティクスが必要な場合、C にはそれを行うための完全に合理的な方法、つまりポインターがありました。

ポインターを使用すると、指定されたオブジェクトの値を変更する意図が明確になります。関数呼び出しを見るだけでこれを確認できます。参照を使用しているかどうかを確認するために関数宣言を確認する必要はありません。

また、参照してください

引数を変更したいのですが、ポインタを使用する必要がありますか?それとも参照を使用する必要がありますか? 明確な論理的理由がわかりません。「オブジェクトではない」(null ポインタなど) を渡すことが許容される場合は、ポインタを使用するのが理にかなっています。私の個人的なスタイルは、オブジェクトを変更したいときにポインターを使用することです。コンテキストによっては、変更が可能であることを簡単に見つけられるからです。

同じ FAQ から

于 2008-09-28T17:45:02.790 に答える
1

正直なところ、この C++ での値渡し/参照渡しの考え方は誤解を招くものだと思います。 すべて値渡しです。次の 3 つのケースがあります。

  1. 変数のローカル コピーを渡す場所

    void myFunct(int cantChangeMyValue)
    
  2. 変数へのポインターのローカル コピーを渡す場所

    void myFunct(int* cantChangeMyAddress) {
        *cantChangeMyAddress = 10;
    }
    
  3. 「参照」を渡す場所ですが、コンパイラの魔法を使用すると、ポインターを渡して毎回逆参照するだけです。

    void myFunct(int & hereBeMagic) {
        hereBeMagic = 10; // same as 2, without the dereference
    }
    

個人的には、すべてが値渡しであることを覚えておくと、混乱がはるかに少ないと思います。場合によっては、その値がアドレスになることがあります。これにより、関数の外部で変更を行うことができます。

あなたが提案していることは、プログラマーが番号1を実行することを許可しません.個人的には、そのオプションを取り除くのは悪い考えだと思います. C/C++ の大きな利点の 1 つは、きめの細かいメモリ管理ができることです。すべてを参照渡しにすることは、単に C++ を Java のようにしようとしているだけです。

于 2008-09-28T17:20:22.677 に答える
1

ええ、それはかなり紛らわしいオーバーロードだと思います。

この状況についてマイクロソフトは次のように述べています。

参照宣言とアドレス取得演算子の使用を混同しないでください。& identifier の前に int や char などの型がある場合、identifier はその型への参照として宣言されます。& 識別子の前に型がない場合、使用法はアドレス取得演算子の使用法です。

私は C や C++ はあまり得意ではありませんが、アセンブラーでコーディングするよりも、両方の言語で * と & のさまざまな使用法を整理する方が頭痛の種になります。

于 2008-09-28T17:21:58.743 に答える
1

最善のアドバイスは、自分が本当にしたいことについて考える習慣をつけることです。参照渡しは、コピー コンストラクターがない場合 (または使用したくない場合) に便利で、大きなオブジェクトの場合は安価です。ただし、パラメーターへの変更は、クラスの外で感じられます。代わりに参照渡しすることもできconstます。その場合、変更はありませんが、ローカルで変更することはできません。関数内で読み取り専用にする安価なオブジェクトには const を値で渡し、ローカルで変更できるコピーが必要な場合は非 const を値で渡します。

各順列 (値による/参照による、および定数/非定数) には、明らかに同等ではない重要な違いがあります。

于 2008-09-28T17:34:57.527 に答える
1

値で渡す場合、データをスタックにコピーしています。渡す構造体またはクラスに対して 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 */
}

Foo オブジェクトを const で始まる関数に渡す場合、PrintStats() を呼び出すことができます。コンパイラは、ChangeStats() の呼び出しでエラーを出します。

void ManipulateFoo( const Foo &foo )
{
    foo.PrintStats();  // Works
    foo.ChangeStats(); // Oops; compile error
}
于 2008-09-28T17:42:51.080 に答える
0

利用可能なすべての種類のパラメーターをそれらのconstバリエーションと混合し始めると、c++は非常に厄介になると思います。

すべてのコピーコンストラクター呼び出し、すべての間接参照のオーバーロードなどを追跡することは、急速に手に負えなくなります。

于 2008-09-29T19:35:11.457 に答える
0

はっきりしないものがあります。あなたが言う時:

int b(b ¶m);

2 番目の「b」は何を意図したものですか? タイプを紹介するのを忘れましたか?最初の 'b' に関して別の書き方をするのを忘れましたか? 次のようなものを書くのは明らかだと思いませんか?

class B{/*something...*/};
int b(B& param);

今から、あなたは私が書いていることを意味していると思います。

さて、あなたの質問は、「コンパイラが非PODのすべての値渡しを参照渡しと見なす方が良いと思いませんか?」です。最初の問題は、それがあなたの契約を破ることです。参照渡しだけでなく、CONST参照渡しを意味していると思います。

あなたの質問は、「値による関数呼び出しを最適化できるコンパイラ ディレクティブがあるかどうか知っていますか?」

今の答えは「わからない」です。

于 2008-09-28T18:36:01.310 に答える