変数を変更する関数があるとします。
void myfunc(int *a)
のように書くべきですか、それともvoid myfunc(int &a)
のように書くべきですか?
前者はmyfunc(&b)
で関数を呼び出すことを強制するので、呼び出し側はb
が変更されることを認識していますが、後者は短く、myfunc(b)
で簡単に呼び出すことができます。それでどちらを使うのが良いですか?他に何か不足していますか?
ポインタ(つまり、「*」)は、「NULL」を渡すことが意味がある場合に使用する必要があります。たとえば、NULLを使用して、特定のオブジェクトを作成する必要があること、または特定のアクションを実行する必要がないことを表すことができます。または、C++以外のコードから呼び出す必要がある場合。 (例:共有ライブラリで使用するため)
例えば。 libc関数time_t time (time_t *result);
result
がNULLでない場合、現在時刻が格納されます。ただし、result
がNULLの場合、アクションは実行されません。
作成している関数が意味のある値としてNULLを使用する必要がない場合、参照(つまり、「&」)の使用は、プロジェクトが使用する規則であると想定すると、おそらく混乱が少なくなります。
可能な限り、ポインタを介した参照を使用します。これは、ポインタよりも参照を台無しにするのがはるかに難しいためです。ポインタ値には常にNULLを渡すことができますが、参照に相当するものはありません。
唯一の欠点は、C++の参照パラメーターに呼び出しサイトのドキュメントがないことです。一部の人々は、それがコードを理解することを難しくすると信じています(そして私はある程度同意します)。私は通常、次のコードをコードで定義し、偽の呼び出しサイトのドキュメントに使用します
#define byref
...
someFunc(byref x);
もちろん、これは呼び出しサイトのドキュメントを強制しません。それは、それを文書化する非常に不十分な方法を提供するだけです。呼び出しサイトのドキュメントを強制するテンプレートを使って実験を行いました。ただし、実際の製品コードよりもおもしろいです。
http://blogs.msdn.com/jaredpar/archive/2008/04/03/reference-values-in-c.aspx
@bbと@JaredParに反対し、フェンスの反対側に寄りかかると思います。他の人々のC++コードを何年もサポートしようと試みた後、参照引数の明らかでない副作用に潜む問題をよく見つけます。 C#では、引数の型の前に 'ref'/'out'を付ける必要があるのは明らかですが、C++では参照が混乱する可能性があります。ポインタが好きなのは、何かが戻ってくることが本当に明らかだからです。ポイントが気に入らない場合は、C++は適していません。
ここや他の場所で引用されている理由により、私はフェンスのポインター側に降りてきます。ただし、どのような決定をしたとしても、一貫性を保ち、スタイルガイドに文書化する必要があります。
Google C++スタイルガイド bans 非const参照引数。
NULLに意味がない場合は、参照渡しが好きですが、両方の引数を確認できます。コーディングに注意している場合は、変数を常にconst参照で渡すようにすることで、偶発的な参照渡しの異議を排除できます。例:
myfunc( const_cast< const int& >( a ) );
// Alternatively, this approach may require additional handling
// in the function, but it's cleaner at call point
myfunc( boost::cref( a ) );
ただし、これは多くの追加コードであり、ほとんどメリットがありません。ケニーが指摘したように、C#は反対側からこれに対処しましたが(参照による特定の受け渡しが必要です)、C++のオプションではありません(たとえば、boost: ref(param))、例えば:
void myfunc( const boost::reference_wrapper< int >& a ) { ... }
しかし、ポインターの問題を修正することはより問題が多くなります...ポインターが有効であることを確認するコンパイル時の方法がないため、ポインターの問題のランタイムの問題、またはランタイムのチェック、あるいはその両方が発生します。ポインターの性質を示します。
とにかく、それは価値があることについての私の意見です。
注意すべき点は、stlファンクタを使用している場合、パラメータがコンテナの値のタイプと一致する方が簡単です。
void foo(Bar *);
void frobnicate(vector<Bar *> vecBars)
{
for_each(vecBars.begin(),
vecBars.end(),
ptr_fun(&foo));
}
上記のコードは、fooがBar&
前者はmyfunc(&b)で関数を呼び出すことを強制するため、呼び出し元はbが変更されることを認識しています
関数がconstポインターを受け入れることがあり、呼び出し元はbが変更されると誤解することがあります。
私の推奨事項-可能な場合はどこでも参照を使用することをお勧めします(もちろん、必要な場合)。関数引数がある場合-私たちは利益を得ます:
-参照をNULLにすることはできません-エラーや不要なアサートまたはチェックを回避するのに役立ちます。
-参照には初期化ポイントが1つだけあり、関数本体では、入力パラメーターが指すものについて常に知っています。
大規模なプロジェクトのメンテナーです。どちらの場合でも、呼び出す前に関数の定義を調べています。もちろん、関数定義を見ると、値、参照、const参照、またはポインターによって引数の定義が表示されます。
しかし、それは聖戦の質問のようであり、異なる人々はこの点について異なる見解を持っています。例えば。 google codding convensionは、変更され、const参照のみが許可される可能性のある引数にポインターを使用することを推奨しました: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Reference_Arguments
参照によって渡されるすべてのパラメーターには、constというラベルを付ける必要があります。
理想的な世界では、パラメータがオプションの出力であるかどうかにかかっています。呼び出し側がNULLを気にしない場合は、それを渡せるようにしますか?次に、ポインターを使用します。それ以外の場合は、リファレンスの方が適しています。
どちらの場合も、言語によって出力パラメータの文書化が面倒になることに注意してください。コードベース全体がconstで正しければ、はるかに優れています。ユーザーは、const以外の参照またはポインターパラメーターが出力であると想定できます。
前述のように、1つの違いは、null参照を渡すことはできませんが、nullポインターを渡すことです。
もう1つ、すでに述べたように、f(a,b)
を呼び出すときに、f
がb
の値を変更する可能性があることを呼び出し元が知らない場合、混乱が生じる可能性があります。
しかし、さらに別の問題ですが、少し微妙ですが、- 私はまだそれに遭遇しました は、参照のセマンティクスです。
ポインタは値で渡されますが、参照は渡されません。
つまり、ポインターでパラメーターを渡すと、ポインターを変更して別のポインターを指すようにすることができます。
このことを考慮:
void f1_ptr( type * a )
{
a = new type(); //no change to passed parameters, you're changing the pointer which was passed by value
}
void f2_ptr( type * a )
{
*a = some_other_value; //now you're changing the value of the parameter that was passed
//or, if type is a class or struct:
a->some_method_that_modifies_object(); //again, changing the parameter that was passed
}
ただし、参照渡しの場合、別の値を参照するように参照を変更することはできません。一度設定された参照は変更できません。
void f3_ref( type& a )
{
a = type(); //the referred variable has also changed
}
//....
type obj = type( params );
f3_ref( obj ); //obj now changed
f1_ptr( &obj ); //obj doesn't change
f2_ptr( &obj ); //obj now changed