web-dev-qa-db-ja.com

C ++の関数にオブジェクトを渡す方法は?

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

ポインター、参照、または非ポインターおよび非参照値を渡す必要がありますか? Javaでは、オブジェクトへの参照を保持する変数のみを渡すため、このような問題はありませんでした。

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

239
Rakesh K

C++ 11:の経験則

by valueを渡します。ただし、

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

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

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


C++ 03:の経験則

引数を渡すby const reference、ただし、

  1. それらは関数内で変更され、そのような変更は外部に反映される必要があります。その場合、nonconst reference
  2. 関数は引数なしで呼び出し可能である必要があります。この場合、ユーザーはNULL/0/nullptrを渡すことができるように、ポインターで渡します。前のルールを適用して、const引数へのポインターを渡す必要があるかどうかを決定します
  3. それらは組み込み型であり、copyで渡すことができます
  4. それらは関数内で変更され、そのような変更はnotが外部に反映されるべきであり、その場合copy byで渡すことができます(別の方法は、前の規則に従って渡し、関数内のコピー)

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


これには他にもありますが、これらのいくつかの初心者向けのルールは、あなたをかなり遠くまで連れて行ってくれます。

266
sbi

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

参照渡し

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

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

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

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

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

constを方程式に追加

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

これは、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 ); 
}

上記のスワップ関数changesは、参照を使用した両方の引数です。 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ポインターです。

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

変更されたパラメーター(「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);

これは興味深いケースです。経験則では、「コピーして安い」タイプは値で渡されます。これらは一般に小さなタイプですが(常にではありません)、その他はconst refで渡されます。ただし、関数に関係なくコピーを作成する必要がある場合は、 値渡し を使用します。 (はい、これにより実装の詳細が少し公開されます。C'est le 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
22
Roger Pate

値渡し:

void func (vector v)

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

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

Const参照による受け渡し:

void func (const vector& v);

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

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

非const参照で渡す:

void func (vector& v)

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

Constの参照ケースのように、これはスレッドセーフではありません。

Constポインターで渡す:

void func (const vector* vp);

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

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

非constポインターで渡す:

void func (vector* vp);

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

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

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

19
nav

誰も言及していないので、オブジェクトを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
}

しかし、これは良いアイデアではありません。メモリリークが発生しやすく、デストラクタを呼び出すことを忘れないポインタを含むコードを大量に書くことになります。そしてこれを避けるために、ポインタを含むオブジェクトが他のオブジェクトデータの操作を停止する関数引数に渡されたときに新しいメモリを作成するコピーコンストラクターを持っていますJavaは値渡しであり、値は参照ですコピーコンストラクタは必要ありません。

0
murali krish