web-dev-qa-db-ja.com

constポインターのポイントは何ですか?

Const値へのポインターについてではなく、constポインター自体について説明しています。

私は非常に基本的なことを超えてCとC++を学んでいますが、今日まで、ポインタは値によって関数に渡されることに気付きました。これは理にかなっています。これは、関数内で、呼び出し元の元のポインターに影響を与えることなく、コピーされたポインターが他の値を指すようにすることができることを意味します。

それで、次のような関数ヘッダーを持つことのポイントは何ですか:

void foo(int* const ptr);

そのような関数の内部では、constであり、変更したくないので、ptrが他の何かを指すことはできませんが、次のような関数です。

void foo(int* ptr);

同様に仕事をします!ポインタはとにかくコピーされ、コピーを変更しても呼び出し側のポインタは影響を受けないためです。 constの利点は何ですか?

145
R. Ruiz.

constは、非常に重要なC++の概念を追求するために使用すべきツールです。

コンパイラに意味を強制させることにより、ランタイムではなくコンパイル時にバグを見つけます。

機能は変更されませんが、constを追加すると、意図していないことをしているときにコンパイラエラーが発生します。次のタイプミスを想像してください:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

int* constを使用すると、値をptrに変更しているため、コンパイラエラーが生成されます。一般に、構文を介して制限を追加することは良いことです。取りすぎないでください-あなたが与えた例は、ほとんどの人がconstを使わないでいる場合です。

200
tenfour

onlyconst引数を使用することを強調します。これにより、より多くのコンパイラーチェックが有効になります。関数内で誤って引数値を再割り当てした場合、コンパイラーが噛みつきます。

変数を再利用することはめったになく、新しい値を保持するために新しい変数を作成するほうがきれいなので、本質的にall私の変数宣言はconstです(constは、コードの機能を妨げます。

これは、関数のdefinitionでのみ意味があることに注意してください。 宣言には属しません。これはユーザーに表示されます。また、ユーザーは、関数内のパラメーターにconstを使用するかどうかは気にしません。

例:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

引数とローカル変数の両方がconstであることに注意してください。どちらも必須ではありませんが、わずかに大きい関数を使用することで、繰り返しミスを防ぎました。

75
Konrad Rudolph

あなたの質問はもっと一般的なものに関係しています:関数の引数はconstである必要がありますか?

値引数の定数(ポインタなど)は実装の詳細であり、関数宣言の一部であるnotを実行します。つまり、関数は常に次のようになります。

void foo(T);

関数スコープの引数変数を変更可能または一定の方法で使用するかどうかは、関数のimplementer次第です。

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

したがって、constを宣言(ヘッダー)に入れないという単純なルールに従い、変数を変更したくない場合や変更する必要がある場合は、定義(実装)に入れてください。

20
Kerrek SB

最上位のconst修飾子は宣言で破棄されるため、質問の宣言はまったく同じ関数を宣言します。一方、definition(実装)では、コンパイラーはポインターをconstとしてマークした場合、関数の本体内で変更されないことを確認します。

constキーワードには多くのことがありますが、かなり複雑です。一般に、プログラムに多くのconstを追加することは、優れたプログラミング手法と見なされます。Webで「const correctness」を検索すると、それに関する多くの情報が見つかります。

Constキーワードはいわゆる「型修飾子」であり、その他はvolatileおよびrestrictです。少なくともvolatileは、constと同じ(混乱させる)ルールに従います。


まず、constキーワードには2つの目的があります。最も明らかな方法は、データ(およびポインター)を意図的または偶発的な誤用から読み取り専用にすることで保護することです。 const変数を変更しようとすると、コンパイル時にコンパイラによって発見されます。

しかし、読み取り専用メモリを備えたシステムには、別の目的もあります。つまり、特定の変数がそのようなメモリ内に割り当てられるようにすることです。たとえば、EEPROMやフラッシュなどです。これらは、不揮発性メモリ、NVMとして知られています。 NVMで割り当てられた変数は、もちろんconst変数のすべての規則に従います。

constキーワードを使用する方法はいくつかあります。

定数変数を宣言します。

これは次のいずれかの方法で実行できます。

const int X=1; or
int const X=1;

これら2つの形式は完全に同等です。後者のスタイルは不適切なスタイルと見なされるため、使用しないでください。

2行目が不適切なスタイルと見なされる理由は、おそらくstaticやexternなどの「ストレージクラス指定子」も宣言できるためですafter実際の型、int staticなど。そのため、ストレージクラスの指定子は、C委員会によって廃止された機能としてラベル付けされます(ISO 9899 N1539ドラフト、6.11.5)。したがって、一貫性を保つために、そのような方法で型修飾子を記述しないでください。それは他の目的には役立ちませんが、とにかく読者を混乱させることです。

定数変数へのポインタを宣言します。

const int* ptr = &X;

これは、「X」の内容を変更できないことを意味します。これは、主に「正確さ」の関数パラメーターの一部として、このようなポインターを宣言する通常の方法です。 「X」は実際にはconstとして宣言する必要がないため、任意の変数を使用できます。つまり、変数をconstにいつでも「アップグレード」できます。技術的には、Cでは明示的な型キャストによってconstからプレーン変数にダウングレードすることもできますが、そうすることは悪いプログラミングと見なされ、コンパイラは通常それに対して警告を出します。

定数ポインタを宣言する

int* const ptr = &X;

これは、ポインターitselfが一定であることを意味します。それが指すものを変更できますが、ポインター自体を変更することはできません。これには多くの用途はありませんが、関数にパラメーターとして渡されている間、ポインターでポイントされた(ポインター間)アドレスが変更されないようにするなど、いくつかあります。次のような読みにくいものを記述する必要があります。

void func (int*const* ptrptr)

多くのCプログラマーがconstと*を適切に取得できるとは思わない。 [〜#〜] i [〜#〜]はできません-GCCで確認する必要がありました。だからこそ、良いプログラミング手法と見なされているにもかかわらず、ポインターツーポインターの構文がめったに見られないのだと思います。

定数ポインタを使用して、ポインタ変数自体が読み取り専用メモリで宣言されていることを確認することもできます。たとえば、ある種のポインタベースのルックアップテーブルを宣言して、NVMで割り当てることができます。

そしてもちろん、他の答えで示されているように、定数ポインタを使用して「定数の正確性」を強制することもできます。

定数データへの定数ポインタを宣言

const int* const ptr=&X;

これは、上記の2つのポインタータイプを組み合わせたもので、それらのすべての属性が両方ともあります。

読み取り専用メンバー関数を宣言する(C++)

これにはC++のタグが付けられているため、クラスのメンバー関数をconstとして宣言できることにも言及する必要があります。これは、関数が呼び出されたときにクラスの他のメンバーを変更することを許可されていないことを意味します。呼び出して構文は次のとおりです。

void MyClass::func (void) const;
14
Lundin

あなたは正しいです、発信者にとっては全く違いはありません。しかし、関数の作成者にとっては、「大丈夫、この点を間違ったものにしないようにする必要があります」というセーフティネットにすることができます。あまり便利ではありませんが、使い物になりません。

プログラムにint const the_answer = 42を含めるのと基本的に同じです。

13
cnicutar

...今日、ポインタは値によって関数に渡されることに気付きました。これは理にかなっています。

(imo)デフォルトとして本当に意味をなさない。より賢明なデフォルトは、再割り当て不可能なポインタ(int* const arg)。つまり、引数として渡されたポインターが暗黙的にconstとして宣言されることを望んでいました。

Constの利点は何ですか?

利点は、引数が指すアドレスを変更するとき、それがかなり簡単ではないときにバグを導入できるように、それが十分に簡単で、時々不明確であるということです。アドレスの変更は一般的ではありません。アドレスを変更することが目的の場合は、ローカル変数を作成する方が明確です。同様に、生のポインタ操作はバグを導入する簡単な方法です。

そのため、引数が指すアドレスを変更する場合は、不変のアドレスで渡し、コピーを作成する方が明確です(これらの非定型の場合)。

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

ローカルを追加することは事実上無料であり、読みやすさを向上させながらエラーの可能性を減らします。

より高いレベルで:引数を配列として操作する場合、通常はより明確で、クライアントが引数をコンテナ/コレクションとして宣言する傾向があるエラーが少なくなります。

一般に、値、引数、およびアドレスにconstを追加することは、コンパイラーが喜んで実施する副作用を常に認識しているとは限らないため、良いアイデアです。したがって、他のいくつかのケースで使用されるconstと同じくらい便利です(たとえば、質問は「なぜconstを宣言する必要があるのですか?」に似ています)。幸いなことに、再割り当てできない参照もあります。

8
justin

メモリマップデバイスがある組み込みシステムまたはデバイスドライバーのプログラミングを行う場合、ポインターが再割り当てされるのを防ぐために(固定ハードウェアアドレスを指すため)、両方の形式の 'const'がよく使用されます。それが指すレジスタが読み取り専用のハードウェアレジスタである場合、別のconstは実行時ではなくコンパイル時に多くのエラーを検出します。

読み取り専用の16ビット周辺チップレジスタは、次のようになります。

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

次に、アセンブリ言語に頼ることなく、ハードウェアレジスタを簡単に読み取ることができます。

input_Word = *peripheral;

6
Dan Haynes

int iVal = 10; int * const ipPtr =&iVal;

通常のconst変数と同様に、constポインターは宣言時に値に初期化する必要があり、その値は変更できません。

これは、constポインターが常に同じ値を指すことを意味します。上記の場合、ipPtrは常にiValのアドレスを指します。ただし、ポイントされている値はまだ非定数であるため、ポインターの逆参照を介してポイントされている値を変更することができます。

* ipPtr = 6; //許可されます、pnPtrは非const intを指すからです

5
jusathr

あなたの質問は、関数への単なるconstポインターパラメーターではなく、constとして変数を定義する理由についての詳細です。ここでは、関数またはメンバー変数またはローカル変数へのパラメーターである場合、変数を定数として定義するときと同じ規則が適用されます。

あなたの特定のケースでは、ローカル変数をconstとして宣言した場合、機能的には他の多くの場合と違いはありませんが、この変数を変更できないという制限があります。

5
zar

(ポインタだけでなく)他のタイプについても同じ質問をすることができます:

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}
5
pmg

Constポインターが非常に適用可能である場所の例は、このように示すことができます。内部に動的配列を持つクラスがあり、その配列へのユーザーアクセスを渡したいが、ポインターを変更する権限を付与したくないと考えてください。考慮してください:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

生成するもの:

入力データ
put data

しかし、これを試してみると:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

我々が得る:

エラー:割り当ての左オペランドとして左辺値が必要です// Dratは再び失敗しました!

したがって、配列の内容は変更できますが、配列のポインタは変更できません。ポインターをユーザーに戻すときに、ポインターの状態が一貫していることを確認する場合に適しています。ただし、キャッチが1つあります。

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

ポインター自体を変更できない場合でも、ポインターのメモリ参照を削除できます。

したがって、メモリ参照が常に何かを指すようにしたい場合(つまり、参照が現在どのように機能するかと同様に、IEを変更することはありません)、非常に適切です。ユーザーにフルアクセスを許可して変更する場合は、非constが最適です。

編集:

GetArray()が正しい値のオペランドであるために割り当てられないというokorz001コメントに注意した後、彼のコメントは完全に正しいですが、ポインターへの参照を返す場合は上記が引き続き適用されます(GetArrayは参照を参照)、たとえば:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

最初に返され、エラーが発生します。

エラー:読み取り専用の場所 'Temp.TestA :: GetArray()'の割り当て

しかし、2番目は、下に潜在的な結果があるにもかかわらず、陽気に発生します。

明らかに、「ポインタへの参照を返したいのはなぜですか」という質問が発生しますか?問題の元のポインターにメモリ(またはデータ)を直接割り当てる必要がある(たとえば、独自のmalloc/freeまたはnew/freeフロントエンドを構築する)まれな例がありますが、それらの例では非const参照です。 constポインターへの参照は、それを保証する状況に遭遇していません(戻り値の型ではなく、const参照変数として宣言されている場合を除きます)。

Constポインターを受け取る関数があるかどうかを検討します(そうでない関数に対して)。

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

Constのエラーは次のメッセージを生成します。

エラー:読み取り専用パラメーター 'Data'の減少

コメントに示されている問題を引き起こしたくない場合を除き、おそらくそれはしたくないので、これは良いことです。 const関数で減分を編集すると、次のようになります。

NonConst:A
定数:B

明らかに、Aは 'Data [1]'ですが、NonConstポインターが減分操作を許可したため、 'Data [0]'として扱われています。 constを実装すると、他の人が書いているように、発生する前に潜在的なバグをキャッチします。

もう1つの主な考慮事項は、参照が指すものを変更できないという点で、constポインターを疑似参照として使用できることです(おそらくこれが実装された方法であるとしたら、1つ驚かされます)。考慮してください:

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

コンパイルしようとすると、次のエラーが生成されます。

エラー:読み取り専用変数「B」の割り当て

Aへの一定の参照が必要な場合、これはおそらく悪いことです。 B = NULLはコメント化されており、コンパイラは喜んで*BしたがってA.これはintでは役に立たないように思えるかもしれませんが、それを参照する変更不可能なポインタが必要な場合に、それを渡すことができるグラフィカルアプリケーションの単一のスタンスがあるかどうかを検討してください。

使い方はさまざまですが(意図しないしゃれを許します)、正しく使用すると、プログラミングを支援するボックス内の別のツールになります。

4
SSight3

Constポインターを関数に渡すことは、とにかく値で渡されるため、ほとんど意味がありません。これは、一般的な言語設計で許可されているものの1つにすぎません。それが意味をなさないという理由だけでそれを禁止することは、言語仕様を作るだけです。大きい。

関数内にいる場合は、もちろん別のケースです。指し示すものを変更できないポインターを持つことは、コードを明確にするアサーションです。

4
Anders Abel

コンパイラーは、このポインターが変更できないことを認識して、関数内でより積極的な最適化を実行できるという利点があると思います。

また、例えば回避します。このポインターを非constポインター参照を受け入れるサブ関数に渡します(したがって、void f(int *&p)のようにポインターを変更できます)が、この場合は有用性が多少制限されることに同意します。

4
MartinStettner

ポインターがconstにならないようにすることについて、ポインターについて特別なことはありません。クラスメンバー定数int値を持つことができるのと同様に、同様の理由で定数ポインターを持つこともできます:指し示されているものを誰も変更しないようにしたい場合。 C++参照はこれにある程度対処しますが、ポインターの動作はCから継承されます。

3
Mark B

これにより、コードが関数本体内のポインターをインクリメントまたはデクリメントするのを防ぐことができると思います。

3