質問

どこまで一緒に行きますか const?ただ関数を作るだけですか? const 必要に応じて、どこでも全力で使いますか?たとえば、単一のブール値パラメーターを取る単純なミューテーターを想像してください。

void SetValue(const bool b) { my_val_ = b; }

あれですか const 実際に役に立つの?個人的にはパラメーターも含めて広範囲に使用することを選択していますが、この場合、それが価値があるかどうか疑問に思います。

省略できることにも驚きました const 関数宣言のパラメータから取得できますが、関数定義に含めることもできます。例:

.h ファイル

void func(int n, long l);

.cppファイル

void func(const int n, const long l)

これには理由がありますか?私には少し珍しいようです。

役に立ちましたか?

解決

その理由は、パラメータの const はデータのコピーを処理するため、関数内でローカルにのみ適用されるためです。これは、関数のシグネチャがとにかく実際には同じであることを意味します。ただし、これを頻繁に行うのはおそらく悪いスタイルです。

私は個人的に、参照パラメータとポインタパラメータを除いて const を使用しない傾向があります。コピーされたオブジェクトの場合、これはあまり重要ではありませんが、関数内で意図を示すため安全である可能性があります。それは本当に判断の余地がある。ただし、私は何かをループするときに const_iterator を使用する傾向がありますが、それを変更するつもりはありません。そのため、参照型の const の正確さが厳密に維持されている限り、それぞれ独自のものを使用すると思います。

他のヒント

「呼び出し元のオブジェクトを変更しないため、引数が値で渡される場合、const は無意味です。」

間違っている。

コードと仮定を自己文書化することです。

コードに多くの人々が取り組んでおり、関数が重要な場合は、できる限りすべてに「const」をマークする必要があります。業界レベルの強度のコードを作成するときは、同僚があらゆる方法であなたを手に入れようとするサイコパスであることを常に想定する必要があります (特に、将来的には自分自身になることが多いため)。

それに、先ほど誰かが言ったように、 かもしれない コンパイラーが物事を少し最適化できるようにします (ただし、それは長期的な目標です)。

時々 (あまりにも頻繁に!) 他人の C++ コードを解きほぐす必要があります。そして私たちは皆それを知っています 他の人の C++ コードは定義上、完全に混乱しています:) そこで、ローカル データ フローを解読するために最初に行うことは、次のとおりです。 定数 コンパイラが吠え始めるまで、すべての変数定義で。これは、const 修飾値の引数も意味します。なぜなら、それらは呼び出し元によって初期化される単なる派手なローカル変数だからです。

ああ、変数があればいいのに 定数 デフォルトで、そして 可変 非const変数には必要でした:)

次の 2 行は機能的に同等です。

int foo (int a);
int foo (const int a);

当然改造は出来なくなります a の体の中で foo 2 番目の方法で定義されている場合でも、外側からは違いはありません。

どこ const 本当に便利なのは、参照パラメータまたはポインタパラメータを使用する場合です。

int foo (const BigStruct &a);
int foo (const BigStruct *a);

これが何を意味するかというと、foo は大きなパラメータ (おそらくギガバイトのデータ構造) をコピーせずに受け取ることができるということです。また、発信者に、「Fooはそのパラメーターの内容を変更しません*」と言います。 Const Referenceを渡すと、コンパイラが特定のパフォーマンス決定を行うこともできます。

*:const 性を捨て去る場合を除きますが、それは別の記事で説明します。

余分な const は API の観点からは良くありません。

値によって渡される組み込み型パラメータのコードに余分な const を挿入する API が乱雑になる ただし、呼び出し元または API ユーザーに対して意味のある約束はしていません (実装が妨げられるだけです)。

API 内に必要のない「const」が多すぎると、次のようになります。泣いオオカミ」と書かれていると、最終的に人々は「const」を無視し始めるでしょう。それは、それがあちこちにあり、ほとんどの場合何の意味もないからです。

API で追加の const に対する「不合理な削減」引数は、最初の 2 つの点に適しています。つまり、より多くの const パラメータが適切な場合、const を指定できるすべての引数には const を指定する必要があります。実際、それが本当に優れている場合は、パラメータのデフォルトを const にし、パラメータを変更する場合にのみ「mutable」のようなキーワードを使用することをお勧めします。

そこで、できる限り const を入れてみましょう。

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

上記のコード行を考えてみましょう。宣言が乱雑で長くなり、読みにくくなるだけでなく、API ユーザーは 4 つの 'const' キーワードのうち 3 つを無視しても問題ありません。ただし、「const」を余分に使用すると、2 行目は潜在的に 危険な!

なぜ?

最初のパラメータの簡単な読み間違い char * const buffer 渡されるデータ バッファ内のメモリは変更されないと思われるかもしれませんが、そうではありません。 余分な「const」は、API について危険で誤った仮定につながる可能性があります スキャンしたり誤読したりした場合。


余分な const は、コード実装の観点からも好ましくありません。

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

FLEXIBLE_IMPLEMENTATION が true でない場合、API は以下の最初の方法で関数を実装しないことを「約束」します。

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

それはとても愚かな約束です。なぜ呼び出し元に何の利益も与えず、実装を制限するだけの約束をする必要があるのでしょうか?

ただし、これらはどちらも同じ関数の完全に有効な実装であるため、片手を不必要に後ろ手に縛っているだけです。

さらに、これは非常に浅い約束であり、簡単に(そして法的に回避できます)。

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

ほら、私はそうしないと約束したにもかかわらず、とにかくラッパー関数を使ってそのように実装しました。それは、映画で悪者が人を殺さないと約束し、代わりに手下に殺すように命令するようなものです。

それらの余分な const には、映画の悪役からの約束以上の価値はありません。


しかし、嘘をつく能力はさらに悪くなります。

偽の const を使用すると、ヘッダー (宣言) とコード (定義) の const が一致しない可能性があることを教えてもらいました。const-happy の支持者たちは、const を定義にのみ入れることができるため、これは良いことだと主張しています。

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

ただし、その逆も真です...偽の const は宣言にのみ入れて、定義では無視できます。これは、API 内の余分な const をさらにひどいもの、ひどい嘘にするだけです。次の例を参照してください。

class foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

余分な const が実際に行うことは、実装者が変数を変更したり、非 const 参照で変数を渡したりするときに、別のローカル コピーまたはラッパー関数の使用を強制することにより、実装者のコードを読みにくくすることだけです。

この例を見てください。どちらが読みやすいでしょうか?2 番目の関数に余分な変数が存在する唯一の理由は、API 設計者が余分な const を投げ込んだためであることは明らかですか?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

ここで何かを学べたら幸いです。余分な const は、API を乱雑にして目障りで、迷惑な小言であり、浅薄で意味のない約束であり、不必要な障害であり、場合によっては非常に危険な間違いにつながります。

const は C++ のデフォルトであるべきでした。このような :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

私が生計のために C++ をコーディングするときは、できる限りすべてを費やしました。const を使用すると、コンパイラが役立つ優れた方法になります。たとえば、メソッドの戻り値を const 化すると、次のようなタイプミスを防ぐことができます。

foo() = 42

あなたが意味したとき:

foo() == 42

foo() が非定数参照を返すように定義されている場合:

int& foo() { /* ... */ }

コンパイラは、関数呼び出しによって返される匿名の一時変数に値を割り当てることを喜んで許可します。それを定数にする:

const int& foo() { /* ... */ }

この可能性を排除します。

このトピックについては、comp.lang.c++.moderated に関する古い「今週の達人」の記事に詳しい議論があります。 ここ.

対応する GOTW 記事は、Herb Sutter の Web サイトで入手できます。 ここ.

値パラメータを const と言います。

次のバグのある関数を考えてみましょう。

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

数値パラメーターが const の場合、コンパイラーは停止し、バグについて警告します。

データとしてのみ存在し、関数によって変更されない参照 (またはポインター) である関数パラメーターに対して const を使用します。つまり、参照を使用する目的が、データのコピーを回避し、渡されたパラメーターの変更を許可しないことである場合です。

例のブール値 b パラメータに const を置くと、実装に制約が課されるだけで、クラスのインターフェイスには影響しません (ただし、通常はパラメータを変更しないことをお勧めします)。

の関数シグネチャ

void foo(int a);

そして

void foo(const int a);

.c と .h を説明するものと同じです。

アサフ

を使用する場合は、 ->* または .* オペレーター、それは必須です。

次のようなものを書くことを防ぎます

void foo(Bar *p) { if (++p->*member > 0) { ... } }

私は今まさにそうするところでしたが、おそらくあなたの意図したとおりにはなりません。

私が言おうとしていたのは、

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

そして私が入れていたら const その間 Bar * そして p, 、コンパイラはそれを私に教えてくれたでしょう。

ああ、大変ですね。一方で、宣言は契約であり、const 引数を値で渡すことは実際には意味がありません。一方、関数の実装に注目すると、引数定数を宣言すると、コンパイラに最適化の機会がさらに与えられます。

値パラメータを「const」とマークすることは、間違いなく主観的なものです。

ただし、実際には、あなたの例のように、値パラメータを const としてマークすることを好みます。

void func(const int n, const long l) { /* ... */ }

私にとって価値があるのは、関数のパラメーター値が関数によって決して変更されないことを明確に示すことです。最初と最後で同じ値になります。私にとって、それは非常に関数型プログラミングのスタイルを維持することの一部です。

短い関数の場合、引数が関数によって変更されないことは通常明らかであるため、そこに 'const' を置くのはおそらく時間とスペースの無駄です。

ただし、より大きな関数の場合、これは実装ドキュメントの形式であり、コンパイラによって強制されます。

「n」と「l」を使用して何らかの計算を行う場合、一方または両方が変更されている場所を見逃したために異なる結果が得られることを恐れることなく、その計算をリファクタリング/移動できると確信できます。

これは実装の詳細であるため、実装で使用されるのと同じ名前の関数パラメータを宣言する必要がないのと同様に、ヘッダーで値パラメータ const を宣言する必要はありません。

呼び出し元のオブジェクトを変更しないため、引数が値によって渡される場合、const は無意味です。

関数の目的が渡された値を変更することでない限り、参照によって渡す場合は const を優先する必要があります。

最後に、現在のオブジェクト (this) を変更しない関数は const として宣言できますし、おそらくそうすべきです。例を以下に示します。

int SomeClass::GetValue() const {return m_internalValue;}

これは、この呼び出しが適用されるオブジェクトを変更しないという約束です。つまり、次のように呼び出すことができます。

const SomeClass* pSomeClass;
pSomeClass->GetValue();

関数が const でない場合、コンパイラ警告が発生します。

おそらくこれは有効な議論ではないでしょう。ただし、関数内で const 変数の値をインクリメントすると、コンパイラでエラーが発生します。」エラー:読み取り専用パラメータの増分」。これは、関数内の変数を誤って変更することを防ぐ方法として const キーワードを使用できることを意味します (これは想定されていない/読み取り専用です)。そのため、コンパイル時に誤ってそれを実行した場合、コンパイラがそれを通知します。このプロジェクトに取り組んでいるのがあなただけではない場合、これは特に重要です。

あなたが言及したケースでは、APIの呼び出し元には影響しないため、一般的には行われません(ヘッダーでは必要ありません)。関数の実装にのみ影響します。

これを行うのは特に悪いことではありませんが、API に影響を与えず、入力作業が増えるため、メリットはそれほど大きくありません。そのため、通常は行われません。

値を渡すパラメータには const を使用しません。呼び出し元は、パラメーターを変更するかどうかを気にしません。これは実装の詳細です。

本当に重要なことは、インスタンスを変更しない場合はメソッドを const としてマークすることです。そうしないと、大量の const_cast<> が生成されたり、メソッドを const にマークすると、const としてマークされるべき他のメソッドが呼び出されるために多くのコードの変更が必要になったりする可能性があるため、これを実行しながら実行してください。

また、ローカル変数を変更する必要がない場合は、ローカル変数を const としてマークする傾向があります。「可動部分」を特定しやすくなり、コードが理解しやすくなると思います。

私は可能な限り const を使用する傾向があります。(または、ターゲット言語に適した他のキーワード。) 私がこれを行うのは、純粋に、コンパイラーが他の方法では実行できない追加の最適化を実行できるようにするためです。これらの最適化が何であるか全くわからないので、たとえそれがばかばかしいと思われる場合でも、私は常にそれを実行します。

私が知っているすべてのことで、コンパイラはconst値パラメーターを非常によく見て、「ねえ、この関数はとにかくそれを変更していないので、参照してクロックサイクルを節約することができます」と言うかもしれません。関数の署名を変更するので、私はそれがそのようなことをすることはないと思いますが、それはポイントになります。もしかしたら、別のスタック操作か何かを行うのかもしれません...重要なのは、わかりませんが、コンパイラーよりも賢くなろうとするのは恥をかくだけであることはわかっています。

C++ には const の正しさという概念が追加されているため、さらに重要になります。

コンパイラの最適化について: http://www.gotw.ca/gotw/081.htm

できる限り const を使用します。パラメータの const は、値を変更してはならないことを意味します。これは、参照渡しの場合に特に役立ちます。const for function は、関数がクラスのメンバーを変更してはならないことを宣言します。

パラメーターが値によって渡される (参照ではない) 場合、通常、パラメーターが const として宣言されているかどうかに大きな違いはありません (参照メンバーが含まれていない限り、組み込み型では問題ありません)。パラメータが参照またはポインタの場合、通常はポインタ自体ではなく、参照先/参照先のメモリを保護する方が良いです(参照自体を const にすることはできないと思いますが、参照先を変更できないのでそれほど重要ではありません)。 。できる限りすべてを const として保護するのが良いようです。パラメータが単なる POD (組み込みタイプを含む) であり、途中で変更される可能性がない場合は、間違いを恐れることなく省略できます (例:あなたの例ではブールパラメータ)。

.h/.cpp ファイル宣言の違いについては知りませんでしたが、ある程度の意味はあります。マシンコードレベルでは、「const」ではないものはないため、(.h 内で) 関数を非 const として宣言すると、コードは (最適化は別として) const として宣言した場合と同じになります。ただし、関数 (.ccp) の実装内の変数の値を変更しないことをコンパイラーに要求するのに役立ちます。変更が可能なインターフェイスから継承しているものの、必要な機能を実現するためにパラメーターを変更する必要がない場合に便利かもしれません。

要約する:

  • 「通常、const-by-valueは無視され、せいぜい誤解を招くものです。」から GOTW006
  • ただし、変数の場合と同様に、.cpp に追加できます。
  • 標準ライブラリは const を使用しないことに注意してください。例えば。 std::vector::at(size_type pos). 。標準ライブラリで十分なものは、私にとっても十分です。

私はそのようなパラメーターにconstを置くことはありません - 誰もがすでにブール値(ブール値とは対照的に)が一定であることを知っているので、それを追加すると、人々は「待って、何ですか?」または、参照によってパラメーターを渡すことさえあります。

const に関して覚えておくべきことは、後でそれを入れようとするよりも、最初から const にするほうがはるかに簡単であるということです。

何かを変更しないようにしたい場合は const を使用します。これは、関数が何を行うか、何を期待するかを説明する追加のヒントです。私は、それらの一部を処理できる C API、特に C-string を受け入れる API をたくさん見てきました。

私はヘッダーよりも cpp ファイル内の const キーワードを省略したいと考えていますが、カットアンドペーストする傾向があるため、両方の場所に保持されます。なぜコンパイラがそれを許可するのかわかりませんが、おそらくコンパイラの問題だと思います。ベストプラクティスは、両方のファイルに const キーワードを入れることです。

いずれにしても関数は変数のコピーのみを変更できるため、値パラメータを「const」にする理由は実際にはありません。

「const」を使用する理由は、より大きなものを渡す場合です (例:多くのメンバーを含む構造体) を参照によって使用します。この場合、関数がそれを変更できないことが保証されます。というか、従来の方法で変更しようとするとコンパイラがエラーを出します。誤って変更されるのを防ぎます。

Const パラメータは、パラメータが参照によって渡される場合、つまり参照またはポインタのいずれかで渡される場合にのみ役立ちます。コンパイラは const パラメータを認識すると、パラメータで使用されている変数が関数本体内で変更されていないことを確認します。なぜ値渡しパラメータを定数にしたいのでしょうか?:-)

パラメーターは値によって渡されるため、呼び出し側関数の観点からは const を指定してもしなくても違いはありません。基本的に、値渡しパラメーターを const として宣言することは意味がありません。

例内のすべての const には目的がありません。C++ はデフォルトで値渡しであるため、関数はこれらの int と boolean のコピーを取得します。関数がそれらを変更したとしても、呼び出し元のコピーは影響を受けません。

したがって、余分なconstは避けたいと思います。

  • 彼らは冗長です
  • 彼らはテキストを乱雑にします
  • 彼らは、それが有用または効率的である可能性がある場合に、私が渡された価値を変えることを妨げます。

この質問が「少し」古いことは承知していますが、私が見つけたので、将来的には他の誰かも質問するかもしれません......それでも、あの可哀そうな奴が私のコメントを読むためにここにリストアップするとは思えない :)

私たちはまだ C スタイルの考え方に限定されすぎているように思えます。OOP パラダイムでは、型ではなくオブジェクトを扱います。Const オブジェクトは、特に (bitwise-const とは対照的に)logical-const の意味で、非 const オブジェクトとは概念的に異なる場合があります。したがって、POD の場合、関数 params の const の正しさが (おそらく) 過剰な注意であっても、オブジェクトの場合はそうではありません。関数が const オブジェクトを操作する場合は、そのように指定する必要があります。次のコードスニペットを考えてみましょう

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

追記:ここでは (const) 参照の方が適切であり、同じ動作が得られると主張するかもしれません。そうですね。他の場所で見ることができたものとは異なる写真を提供するだけです...

VB.NET プログラマは、50 以上の公開関数を含む C++ プログラムと、const 修飾子を散発的に使用する .h ファイルを使用する必要があるため、ByRef または ByVal を使用して変数にアクセスするタイミングを知るのは困難です。

もちろん、プログラムは、間違いを犯した行で例外エラーを生成することによって通知しますが、その後、2 ~ 10 個のパラメータのうちどれが間違っているかを推測する必要があります。

そこで私は、すべての VB.NET 関数定義を簡単に作成できる自動化された方法で変数を (.h ファイル内で) 定義する必要があることを開発者に説得するという、嫌な仕事に取り組んでいます。そして彼らは独りよがりにこう言います、「...を読んでください」ドキュメンテーション。"

.h ファイルを解析し、すべての Declare Function コマンドを作成する awk スクリプトを作成しましたが、どの変数が R/O か R/W であるかを示すインジケーターがなければ、仕事の半分しか実行できません。

編集:

別のユーザーの勧めで、以下を追加します。

これは、(IMO) 不適切に形成された .h エントリの例です。

typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );

私のスクリプトから得られた VB。

    Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer

最初のパラメータに「const」が欠落していることに注意してください。それがなければ、プログラム(または別の開発者)には、最初のパラメーターが「byval」に渡す必要があるとは知りません。 「const」を追加することにより、他の言語を使用して開発者が作業コードを簡単に書き込むことができるように、.hファイルの自己文書化になります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top