29

最初のプログラミング言語は Java でしたが、大学が変わったので C++ を学んでいます。

Java から来て、C++ の基礎を学んでいる私は、参照と参照変数について読みました。そして、それらがどれほど危険であり、どのように注意するかなど.

だから私の頭の中で 1 つの単純な質問が発生します: なぜ私はそのような複雑な、したがって潜在的に問題を引き起こすものをわざわざ使用する必要があるのでしょうか?

それはどういうわけかそれだけの価値があるのでしょうか、それとも RAM が約 64MB だった時代の遺物なのでしょうか?

多くの回答がポインタに言及しているので、その概念は明らかに石器時代からのものです。高性能計算を除いて、私はそのようなものには触れません。

4

8 に答える 8

14

この問題は、参照自体には関係ありません。

問題は、C++ では、ガベージ コレクターを使用する Java やその他のランタイム環境とはオブジェクトの有効期間の管理が異なることです。C++ には、標準の組み込みガベージ コレクタがありません。C++ オブジェクトの有効期間は、自動 (ローカルまたはグローバル スコープ内) または手動 (ヒープで明示的に割り当て/割り当て解除) にすることができます。

C++ 参照は、オブジェクトの単なるエイリアスです。オブジェクトの有効期間については何も知りません (効率のため)。プログラマーはそれを気にする必要があります。例外は、参照が一時オブジェクトにバインドされる特殊なケースです。この場合、一時オブジェクトの存続期間は、バインドされた参照の存続期間まで延長されます。詳細はこちら

参照は C++ の基本概念の重要な部分であり、タスクの 90% で参照を使用することを避けることはできません。それ以外の場合は、ポインターを使用する必要がありますが、これは通常さらに悪いことです:-)

たとえば、ではなく参照によってオブジェクトを関数の引数として渡す必要がある場合は、参照を使用できます。

void f(A copyOfObj);       // Passed by value, f receives copy of passed A instance
void f(A& refToObj);       // Passed by ref, f receives passed A instance itself, modifiable
void f(const A& refToObj); // Passed by const ref, f receives passed A instance itself, non modifiable
于 2012-10-04T14:12:00.527 に答える
8

大きなオブジェクトを関数パラメーターとして値渡しすると、非常にパフォーマンスが低下する可能性がありますが、参照として渡すことで解決できます。また、値ではなく参照で渡す場合は、関数内のオブジェクトを変更できます。

于 2012-10-04T13:58:52.997 に答える
6

参照は、制限付きのポインターです。参照はオブジェクトにアクセスする方法ですが、オブジェクト自体ではありません。コードでこれらの制限を使用することが理にかなっている場合は、ポインターの代わりに参照を使用すると、コンパイラーは誤って制限に違反していることを警告できます。

于 2012-10-04T14:00:08.863 に答える
4

あなたは Java から来たばかりなので、C/C++ でできる多くの危険なことに問題があると想像できます。私の意見では、違いは次のとおりです。

  • C/C++ を使用すると、多くの強力危険なことを行うことができます。あなたは多くの素晴らしいことをすることができますが、自分自身を撃つこともできます
  • Javaは、安全のためにできることを制限します

あなたが気に入らないこと (「ポインター/参照は危険」) は、まさに私が C++ について愛していることです (「多くのことを実行でき、失敗した場合は私のせいです」)。私の意見では、それはプログラミング言語の好みの問題です。

ポインターのいくつかの危険を回避するために参照が作成されましたが、一方で、ポインターでさえ、明らかな高速アクセス以外の用途があります。たとえば、いくつかの有用な作業を行い、複数の高レベル オブジェクト間で共有される低レベル オブジェクトがあるとします。さまざまな理由で共有できます(たとえば、有用な作業を行うたびに内部状態を更新するため、コピーすることはできません)。

class HL{
    private:
    LL &object;

    public:
    HL(LL &obj){
        object = obj; // initialization of reference
    }
    // a no-argument constructor is not possible because you HAVE to
    // initialize the reference at object-creation (or you could initialize
    // it to a dummy static object of type LL, but that's a mess)
    void some_routine(){
        ....
        object.useful_work();
        ...
    }
}

この場合に参照を使用すると、 internal の初期化が強制objectされます。一方、オブジェクトが提供する機能が高レベルのオブジェクトにとって単なるオプションである場合、ポインターは次の方法で使用できます。

class HL{
    private:
    LL *object;

    public:
    HL(LL &obj){
        object = &obj; // initialization of reference
    }
    HL(){} // no-argument constructor possible
    void some_routine(){
        ....
        if (object != NULL)
           object->useful_work();
        ...
    }
}

また、参照渡しの使用に関しては、大きな構造体を関数に渡す場合に最も便利ですvector<int>

void function(vector <int> a); // call by value, vector copied every time
void function(vector <int> &a); // call by reference, you can modify
                                // the vector in the function
void function(const vector <int> &a); // if you don't want to be able to modify
                                      // it, you can add const for "protection"
于 2012-10-04T14:27:30.963 に答える
3

参照の最も頻繁な使用は、コストのかかるディープ コピーを回避することです。デフォルトでは、C++ には値のセマンティクスがあり、次のように記述した場合:

void f( std::vector<double> d );

を呼び出すたびに、ベクトル全体がコピーされますf。したがって、次のように記述します。

void f( std::vector<double> const& d );

これが特に重要な場所の 1 つは、コピー コンストラクターを定義するときです。あなたが書くなら:

MyType( MyType other );

、コンパイラは引数をこのコンストラクターにコピーするためにコピーコンストラクターを呼び出す必要があるため、無限再帰になります。(実際、コンパイラは、正確にこの問題を回避するために、コピー コンストラクタへの参照を取るコンストラクタのみを受け入れます。)

コンストラクター以外では、これは実際には最適化であり、時期尚早の最適化と見なすことができます。ただし、実際には、値ではなく const への参照によってクラス型を渡すことが、ほぼ普遍的な規則です。

参照は出力パラメータとしても使用できます。ポインターと比較して、null にできないという利点があります。

参照は、クラス データへの「ウィンドウ」を提供するためにも使用できます。古典的な例は、 in のオーバーロードoperator[]ですstd::vector。配列内の実際の要素を変更したり、アドレスを取得したりできるように、参照を返します。

上記の場合、パラメータや戻り値以外で参照することはやや稀です。たとえば、クラス メンバーとして 1 つを使用する場合、クラスに従来の代入セマンティクスを与えることはできないことに注意する必要があります。(クラスが代入をサポートしていない場合、これは問題になりません。) グローバル変数として、参照されているものの実装を隠すために使用される場合がありますが、これは非常にまれです。ローカル変数として、私が知っている唯一の用途は、参照を返す別の式の結果をキャッシュすることです。たとえば、someMap[aKey]短いコード ブロックで何度もアクセスしている場合、 :

ValueType& element = someMap[aKey];

最初に、elementその後に使用します。someMap[aKey](参照を返すため、これは意味があることに注意してください。)

于 2012-10-04T14:19:17.363 に答える
3

実際の問題は、「なぜポインターの代わりに参照を使用するのか」です。

私はまだそれをよく理解していません。現在、私は参照を使用しています

  • 割り当てることができないからです。最悪の場合、これはコンパイラーにとって何の意味もありませんが、コンパイラーがその知識を利用できる可能性があります。

  • 多くの場合、常に存在するポインターを返したい場合、またはパラメーターが必要で、nullptr ではない可能性がある (null 参照がない) ことを示したい場合に使用します。難しいルールではありません。実際のポインターの方が自然に感じる場合もあります。

  • 演算子のオーバーロードのように、物事が単に参照を要求する場合。

実際のコピーが本当に必要でない限り、私は一般的にオブジェクトをコピーしたくないので、私は多くの参照を使用することになり、参照はしばしば機能します。

于 2012-10-04T14:08:17.137 に答える
1

&参照は、オブジェクトを渡すときに呼び出し元が演算子を使用する必要がないため、参照による呼び出しを容易にします。

このような:

MyObject ob;

void foo_ref(MyObject& o){ ...}
void foo_ptr(Myobject* o){ ...}

//call

foo_ref(ob);
foo_ptr(&ob);

さらに、関数内のポインターの初期化を有効にします。

MyObject* ob = nullptr;
MySubject* sub = nullptr;

void foo_init(MyObject *& o, MySubject *& s) {

   o = new MyObject;
   s = new MySubject;
}

//call

foo_init(ob, sub);

ポインターへの参照がないと、この例はポインターへのポインターでのみ機能し、最初に各引数を逆参照する必要があるため、コードがひどいものになります。

MyObject* ob = nullptr;
MySubject* sub = nullptr;

void foo_init(MyObject ** o, MySubject ** s) {

   *o = new MyObject;
   *s = new MySubject;
}

//call

foo_init(&ob, &sub);
于 2012-10-04T14:08:43.640 に答える
0

たとえば、初期化を強制しているため、危険性が低くなります

于 2012-10-04T14:00:57.053 に答える