web-dev-qa-db-ja.com

C ++:Constの正確さとポインター引数

Constポインタはいくつかの方法で宣言できることを理解しています。

const int * intPtr1; // Declares a pointer that cannot be changed.
int * const intPtr2; // Declares a pointer whose contents cannot be changed.

// EDIT: THE ABOVE CLAIMS ARE INCORRECT, PLEASE READ THE ANSWERS.

しかし、関数の引数のコンテキスト内で同じ原理はどうでしょうか?

以下は冗長だと思います。

void someFunc1(const int * arg);
void someFunc2(int * arg);

SomeFunc 1および2はポインター自体に値渡しを行うため、関数への特定の呼び出しでsomeFunc1が元のポインターの値を変更することは不可能です。説明する:

int i = 5;
int * iPtr = &i;

someFunc1(iPtr); // The value of iPtr is copied in and thus cannot be changed by someFunc1.

これらが真である場合、「const int * ptr」型の引数を持つ関数を宣言する意味はありません、正しいですか?

21
Ben

あなたはそれを逆に持っています:

const int * intPtr1; // Declares a pointer whose contents cannot be changed.
int * const intPtr2; // Declares a pointer that cannot be changed.

次のconstは確かに不要であり、関数に入れる理由はありませんdeclaration

void someFunc1(int * const arg);

ただし、ローカル変数(またはその他)constを宣言するのと同じ理由で、関数implementationに配置することをお勧めします-実装はより簡単かもしれません特定の事柄が変わらないことがわかっているときにフォローする。関数の他の宣言でconstが宣言されているかどうかに関係なく、これを行うことができます。

34
Mike Seymour

まあ、それは呼び出し側ではなく、someFunc1。したがって、someFunc1誤って変更しないでください。お気に入り

void someFunc1(int *arg) {
  int i = 9;
  arg = &i;  // here is the issue
  int j = *arg;
}

いくつかのケーススタディをしましょう:

1)先のとがった値をconstにするだけ

void someFunc1(const int * arg) {
int i = 9;
*arg = i; // <- compiler error as pointed value is const
}

2)ポインタをconstにするだけ

void someFunc1(int * const arg) {
int i = 9;
arg = &i; // <- compiler error as pointer is const
}

)関係する変数がconstである可能性がある場合にconstを使用する正しい方法:

void someFunc1(const int * const arg) {
    int i = 9;
    *arg = i; // <- compiler error as pointed value is const
    arg = &i; // <- compiler error as pointer is const
}

これですべての疑問が解消されます。したがって、これは関数コード用であり、呼び出し元用ではないことをすでに述べたので、上記の3つのケースの中で最も制限的なものを使用する必要があります。

編集:

  • 関数の宣言でも、constを宣言することをお勧めします。これにより、読みやすさが向上するだけでなく、呼び出し元もコントラクトを認識できるようになり、引数の不変性に関する信頼性が高まります。 (これは、一般的にヘッダーファイルを共有するために必要なbcozであるため、呼び出し元が実装のc/cppファイルを持たない場合があります)
  • 宣言と定義の両方が同期していれば、コンパイラでさえより適切に指摘できます。
25
havexz

あなたはあなたの論理を間違った方法で持っています。型を逆に読む必要があるため、const int *const intへのポインターであり、int * constはintへのconstポインターです。

例:

void foo() {
    int a = 0;
    int b = 0;

    int * const ptrA = &a;
    *ptrA = 1;
    ptrA = &b; ///< Error

    const int * ptrB = &a;
    *ptrB = 1; ///< Error
    ptrB = &b;

    const int * const ptrC = &a;
    *ptrC = 1; ///< Error
    ptrC = &a; ///< Error
}

関数パラメーターをconst int *にする理由を詳しく説明するには、関数として値を変更する必要があるため、呼び出し元にintを渡す必要があることを示す必要があります。たとえば、次のコードを検討してください。

void someFunc1(const int * arg) {
    // Can't change *arg in here
}

void someFunc2(int * arg) {
    *arg = 5;
}

void foo() {
    int a = 0;
    someFunc1(&a);
    someFunc2(&a);

    const int b = 0;
    someFunc1(&b);
    someFunc2(&b); ///< *** Error here. Must pass in an int not a const int.
}
7
mattjgalloway

はい、あなたは正しいです(あなたがそれらを間違った方法で入手したという事実を無視して)-非参照constパラメータをとっても意味がありません。さらに、参照以外のconst値を返す意味はありません。

2
Puppy

あなたはそれを間違った方法で持っています:

_const int * intPtr1; // Declares a pointer whose contents cannot be changed.
int * const intPtr2; // Declares a pointer that cannot be changed.
_

一般的に言えば、その表現を少し異なるように書くときの一貫性について推論するのは簡単です:_const int*_は_int const *_と同じ型です。その表記では、規則がはるかに明確になり、constは常にその前の型に適用されるため、次のようになります。

_int const * intPtr1; // Declares a pointer to const int.
int * const intPtr2; // Declares a const pointer to int.
int const * * const * complexPtr; // A pointer to const pointer to pointer to const int
_

型が先頭にconstを付けて書き込まれる場合、constは最初の型の後に書き込まれたものとして処理されるため、_const T*_は_T const *_になります。

_void someFunc2(int * arg);
_

したがって、_someFunc2_はargの内容を変更する可能性がありますが、_someFunc1_は変更しない可能性があるため、冗長ではありません。 void someFunc3(int * const arg);は冗長です(あいまいです)

2
Grizzly