266

私はC++プログラミングに不慣れですが、Javaの経験があります。C++の関数にオブジェクトを渡す方法についてのガイダンスが必要です。

ポインター、参照、または非ポインターと非参照の値を渡す必要がありますか?Javaでは、オブジェクトへの参照を保持する変数だけを渡すので、そのような問題はないことを覚えています。

これらの各オプションをどこで使用するかも説明できれば素晴らしいと思います。

4

8 に答える 8

294

C ++ 11の経験則:

次の場合を除いて、を渡す

  1. オブジェクトの所有権は必要ありません。単純なエイリアスで十分です。この場合、参照渡します。const
  2. オブジェクトを変更する必要があります。その場合は、非左辺値参照によるパスをconst使用します。
  3. 派生クラスのオブジェクトを基本クラスとして渡します。この場合、参照で渡す必要があります。(前のルールを使用して、const参照で渡すかどうかを決定します。)

ポインタを渡すことは、事実上決してお勧めできません。std::optionalオプションのパラメーターは(古い標準ライブラリの場合)として最もよく表現され、boost::optionalエイリアシングは参照によって正常に実行されます。

C ++ 11の移動セマンティクスにより、複雑なオブジェクトの場合でも、値による受け渡しがはるかに魅力的になります。


C ++ 03の経験則:

引数const参照で渡す(次の場合を除く)

  1. それらは関数内で変更され、そのような変更は外部に反映される必要があります。その場合、非参照で渡します。const
  2. 関数は引数なしで呼び出すことができる必要があります。その場合、ユーザーが代わりに//を渡すことができるように、ポインターを渡しますNULL前のルールを適用して、引数へのポインターを渡す必要があるかどうかを判断します0nullptrconst
  3. それらは組み込みタイプであり、コピーで渡すことができます
  4. それらは関数内で変更され、そのような変更は外部に反映されるべきではありません。その場合、コピーで渡すことができます(別の方法は、前のルールに従って渡し、関数内でコピーを作成することです)

(ここで、「値渡し」は「コピー渡し」と呼ばれます。これは、値渡しによって常にC ++ 03でコピーが作成されるためです)


これにはまだまだありますが、これらのいくつかの初心者のルールはあなたをかなり遠ざけるでしょう。

于 2010-01-26T12:15:06.330 に答える
114

C++とJavaの呼び出し規約にはいくつかの違いがあります。C ++では、技術的に言えば、値渡しと参照渡しの2つの規則しかなく、3番目のポインター渡し規則(実際にはポインター型の値渡し)を含むいくつかの文献があります。さらに、引数の型に定数を追加して、セマンティクスを強化できます。

参照による通過

参照渡しとは、関数が概念的にオブジェクトインスタンスを受け取り、そのコピーを受け取らないことを意味します。参照は、概念的には、呼び出し元のコンテキストで使用されたオブジェクトのエイリアスであり、nullにすることはできません。関数内で実行されるすべての操作は、関数外のオブジェクトに適用されます。この規則は、JavaまたはCでは使用できません。

値渡し(およびポインター渡し)

コンパイラーは、呼び出し元のコンテキストでオブジェクトのコピーを生成し、そのコピーを関数内で使用します。関数内で実行されるすべての操作は、外部要素ではなく、コピーに対して実行されます。これは、Javaのプリミティブ型の規則です。

その特別なバージョンは、ポインタ(オブジェクトのアドレス)を関数に渡すことです。関数はポインタを受け取り、ポインタ自体に適用されるすべての操作はコピー(ポインタ)に適用されますが、逆参照されたポインタに適用される操作はそのメモリ位置のオブジェクトインスタンスに適用されるため、関数は副作用があります。オブジェクトへのポインターの値渡しを使用する効果により、参照渡しの場合と同様に、内部関数が外部値を変更できるようになり、オプションの値(nullポインターを渡す)も可能になります。

これは、関数が外部変数を変更する必要がある場合にCで使用される規則であり、参照型を使用してJavaで使用される規則です。参照はコピーされますが、参照されるオブジェクトは同じです。参照/ポインターへの変更は外部に表示されません。関数ですが、指定されたメモリへの変更はあります。

方程式にconstを追加する

C ++では、さまざまなレベルで変数、ポインター、および参照を定義するときに、オブジェクトに定数を割り当てることができます。変数を定数として宣言したり、定数インスタンスへの参照を宣言したり、定数オブジェクトへのすべてのポインター、可変オブジェクトへの定数ポインター、定数要素への定数ポインターを定義したりできます。逆に、Javaでは、定数の1つのレベル(finalキーワード)のみを定義できます。変数のレベル(プリミティブ型のインスタンス、参照型の参照)ですが、不変要素への参照を定義することはできません(クラス自体が不変)。

これは、C++の呼び出し規約で広く使用されています。オブジェクトが小さい場合は、オブジェクトを値で渡すことができます。コンパイラーはコピーを生成しますが、そのコピーはコストのかかる操作ではありません。他のタイプの場合、関数がオブジェクトを変更しない場合は、そのタイプの定数インスタンス(通常は定数参照と呼ばれます)への参照を渡すことができます。これはオブジェクトをコピーしませんが、それを関数に渡します。しかし同時に、コンパイラはオブジェクトが関数内で変更されないことを保証します。

経験則

これは従うべきいくつかの基本的なルールです:

  • プリミティブ型には値渡しを優先する
  • 他のタイプの定数への参照を使用した参照渡しを優先する
  • 関数が引数を変更する必要がある場合は、参照渡しを使用してください
  • 引数がオプションの場合は、ポインタ渡しを使用します(オプションの値を変更しない場合は定数に)

これらのルールには他にも小さな逸脱があり、その最初のルールはオブジェクトの所有権の処理です。オブジェクトがnewで動的に割り当てられる場合、delete(またはその[]バージョン)で割り当てを解除する必要があります。オブジェクトの破棄を担当するオブジェクトまたは関数は、リソースの所有者と見なされます。動的に割り当てられたオブジェクトがコードの一部で作成されたが、所有権が別の要素に譲渡された場合、通常はポインター渡しのセマンティクス、または可能であればスマートポインターを使用して行われます。

サイドノート

C++とJavaの参照の違いの重要性を主張することが重要です。C ++では、参照は概念的にはオブジェクトのインスタンスであり、オブジェクトへのアクセサーではありません。最も簡単な例は、スワップ関数の実装です。

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

上記のスワップ関数は、参照を使用して両方の引数を変更します。Javaで最も近いコード:

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

Javaバージョンのコードは、参照のコピーを内部的に変更しますが、実際のオブジェクトを外部的に変更することはありません。Java参照は、値によって関数に渡されるポインター演算のないCポインターです。

于 2010-01-26T13:17:58.900 に答える
24

考慮すべきいくつかのケースがあります。

変更されたパラメーター(「out」および「in / out」パラメーター)

void modifies(T &param);
// vs
void modifies(T *param);

このケースは主にスタイルに関するものです。コードをcall(obj)またはcall(&obj)のように見せたいですか?ただし、違いが重要になる2つのポイントがあります。以下のオプションの場合と、演算子をオーバーロードするときに参照を使用する場合です。

...そしてオプション

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

パラメータは変更されていません

void uses(T const &param);
// vs
void uses(T param);

これは興味深いケースです。経験則では、「コピーするのが安い」タイプは値で渡されます—これらは一般に小さいタイプです(常にではありません)—他のタイプはconstrefで渡されます。ただし、関数内でコピーを作成する必要がある場合は、値を渡す必要があります。(はい、これにより実装の詳細が少し明らかになります。C'estle C ++。

...そしてオプション

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

ここではすべての状況の違いが最も少ないので、あなたの人生を最も簡単にするものを選択してください。

値による定数は実装の詳細です

void f(T);
void f(T const);

これらの宣言は実際にはまったく同じ機能です! 値を渡す場合、constは純粋に実装の詳細です。 やってみよう:

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types
于 2010-10-19T15:00:46.147 に答える
22

値渡し:

void func (vector v)

関数が環境から完全に分離する必要がある場合、つまり関数が元の変数を変更するのを防ぎ、関数の実行中に他のスレッドがその値を変更するのを防ぐために、変数を値で渡します。

欠点は、オブジェクトのコピーに費やされるCPUサイクルと余分なメモリです。

const参照を渡す:

void func (const vector& v);

このフォームは、コピーのオーバーヘッドを取り除きながら、値渡しの動作をエミュレートします。関数は元のオブジェクトへの読み取りアクセスを取得しますが、その値を変更することはできません。

欠点はスレッドセーフです。別のスレッドによって元のオブジェクトに加えられた変更は、実行中に関数内に表示されます。

非定数参照を渡す:

void func (vector& v)

これは、関数が変数に値を書き戻す必要がある場合に使用します。これは、最終的に呼び出し元によって使用されます。

constリファレンスの場合と同様に、これはスレッドセーフではありません。

constポインタを渡します:

void func (const vector* vp);

構文が異なることと、呼び出し元の関数がNULLポインターを渡して、渡す有効なデータがないことを示すことができることを除いて、const-referenceによる渡と機能的に同じです。

スレッドセーフではありません。

非定数ポインタを渡す:

void func (vector* vp);

非定数参照に似ています。関数が値を書き戻すことになっていない場合、呼び出し元は通常、変数をNULLに設定します。この規則は、多くのglibcAPIで見られます。例:

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

すべてが参照/ポインタを通過するのと同じように、スレッドセーフではありません。

于 2014-11-06T14:26:51.337 に答える
0

誰も言及していないので、オブジェクトをc ++の関数に渡すと、オブジェクトのクローンを作成してメソッドに渡すオブジェクトがない場合は、オブジェクトのデフォルトのコピーコンストラクターが呼び出されます。元のオブジェクトではなくオブジェクトのコピーに反映されるオブジェクト値を変更すると、これはc ++の問題です。したがって、すべてのクラス属性をポインターにすると、コピーコンストラクターはのアドレスをコピーします。ポインタ属性。したがって、ポインタ属性アドレスに格納されている値を操作するオブジェクトに対するメソッド呼び出しの場合、変更はパラメータとして渡される元のオブジェクトにも反映されるため、これはJavaと同じように動作できますが、すべてのクラスを忘れないでください。属性はポインタである必要があります。また、ポインタの値を変更する必要があります。コードの説明で非常に明確になります。

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

ただし、これは、メモリリークが発生しやすく、デストラクタを呼び出すことを忘れないポインタを含む多くのコードを記述してしまうため、お勧めできません。また、このc ++にはコピーコンストラクターがあり、ポインターを含むオブジェクトが他のオブジェクトデータの操作を停止する関数の引数に渡されたときに新しいメモリを作成します。Javaは値を渡し、値は参照であるため、コピーコンストラクターは必要ありません。

于 2015-02-04T06:03:33.837 に答える
0

ポインター、参照、または非ポインターと非参照の値を渡す必要がありますか?

これは、関数を記述し、それが取るパラメーターのタイプを選択するときに重要な質問です。その選択は、関数の呼び出し方法に影響し、いくつかのことに依存します。

最も簡単なオプションは、オブジェクトを値で渡すことです。これは基本的に関数内にオブジェクトのコピーを作成し、多くの利点があります。ただし、コピーにコストがかかる場合もあります。その場合const&は、通常、定数参照が最適です。また、関数によってオブジェクトを変更する必要がある場合もあります。次に、非定数の参照、、&が必要です。

パラメータタイプの選択に関するガイダンスについては、F.15以降C++コアガイドラインの関数セクションを参照してください。原則として、生のポインタは避けてください。*

于 2021-01-27T12:27:08.967 に答える
-1

オブジェクトをパラメータとして関数に渡すには、次の3つの方法があります。

  1. 参照による通過
  2. 値渡し
  3. パラメータに定数を追加する

次の例を実行します。

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

出力:

私がsomeFuncにいるとし
ましょうポインタ
の値は-17891602です変数の値は4です

于 2011-03-09T17:24:30.060 に答える
-2

以下は、C++で機能する引数/パラメーターを渡す方法です。

1.値による。

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2.参照による。

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3.オブジェクト別。

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}
于 2015-12-04T06:12:29.980 に答える