7

変数を作成する" constify" 演算を使用することは理にかなっていますか?C/C++const

constこれが役立つ例を次に示します。最初の行でまだ宣言したくないのは明らかです。

std::vector<int> v;
v.push_back(5);
constify v; // now it's const

現在、そのような可能性がなければ、同じ効果を得るために別の変数を導入する必要があります:

std::vector<int> v0;
v0.push_back(5);
const std::vector<int>& v = v0;

スコープに新しい名前を追加し、ベクトル全体をコピーしないように参照する必要があるため (またはswap? を使用する必要があるため)、さらに混乱します。

4

10 に答える 10

19

率直に言って、変数がどちらかであるかどうかは、これが変化する可能性がある場合よりも混乱が少ないと思います。const


これについて少し詳しく説明します。通常、これを行う理由は、const変数を思い通りに初期化できないためです。std::vectorはこれの良い例です。さて、次の標準では、これを可能にする普遍的な初期化構文が導入されています。

const std::vector<int> cvi = { 1, 2, 3, 4, 5, 42 }; 

ただし、手元に C++1x のものがなくても、この初期化構文を許可しない型であっても、必要なことを行うヘルパー関数をいつでも作成できます。

const std::vector<int>& cvi = create_my_vector();

または、派手になりたい場合:

const std::vector<int>& cvi = compile_time_list<1,2,3,4,5,42>::create_vector();

に注意してください&。関数呼び出しの結果をコピーしても意味がありません。右辺値をconst参照にバインドすると、その有効期間が参照の有効期間の終わりまで延長されるためです。
もちろん、C++1x の移動セマンティクスをサポートするコンパイラで再コンパイルすると、そのような最適化はほとんど不要になります。ただし、rvlaue をconst参照にバインドすることは、ベクトルを移動するよりも高速である可能性があり、遅くなる可能性はほとんどありません。
C++1x では、これをその場で実行するラムダ関数を作成することもできます。C++ は、信じられないほど膨大な数のツールを提供します。IME、あなたがいくら考えても、同じことをする別のアイデアを誰かが思いつくはずです。そして、多くの場合、あなたのものよりも優れています。


ただし、IME では通常、コードが多すぎて関数が少なすぎる場合にのみ、この問題が発生します。そして、これは constness だけでなく、同様の特性 (参照が参照するものなど) にも適用されます。
古典的なのは、複数の可能なストリームのうちの 1 つを使用することです。これの代わりに

int main(int argc, char* argv[])
{
  std::istream* istrm = NULL;
  std::ifstream ifs;
  if( argc > 1 )
  {
    ifs.open( argv[1] );
    if( ifs.good() ) 
      istrm = &ifs;
  }
  if( !istrm ) 
    istrm = &std::cin;

  while( istrm->good() )
  {
     // reading from *istrm implemented here
  }
  return 0;
}

懸念事項を 1) どこから読み取るかを把握することと 2) 実際の読み取りに分割するだけです。

int read(std::istream& is)
{
  while( is.good() )
  {
     // reading from is implemented here
  }
  return 0;
}

int main(int argc, char* argv[])
{
  if( argc > 1 )
  {
    std::ifstream ifs( argv[1] );
    if( ifs.good() ) 
      return read(ifs);
  }
  return read(std::cin);
}

懸念を分離することによって修正できなかった、可能な限り一定ではなかった変数の実際の例をまだ見たことがありません。

于 2010-08-25T16:34:20.823 に答える
8

基本的に、コンストラクターの効果を再現しようとしています。つまり、constコンストラクターが完了した後にのみ適用されます (および dtor が呼び出されるまでのみ)。そのため、必要なのは、ベクターをラップして ctor で初期化する別のクラスです。ctor が完了して戻ると、インスタンスは次のようになりますconst(もちろん、 と定義されていると仮定しますconst)。

C++0x では、このようなラッピングの要件が大幅に改善されます。ベクトルのブレース初期化子を使用して、1 回の操作でベクトルを作成/初期化できます。他のタイプは、(少なくとも潜在的に) ユーザー定義の初期化子をサポートして、ほぼ同じことを達成します。

于 2010-08-25T16:39:28.523 に答える
7

C++ は静的に型付けされます。私にとって、そのような操作を導入することは、このパラダイムに違反し、多くの混乱を引き起こすでしょう.

于 2010-08-25T16:40:00.347 に答える
6

これは、関数を使用する絶好の機会です

#include <vector>

std::vector<int> makeVector()
{
  std::vector<int> returnValue;
  returnValue.push_back(5);
  return returnValue;
}

int main()
{
  const std::vector<int> myVector = makeVector();
}
于 2010-08-25T16:34:46.503 に答える
4

ベクトルの初期化(C ++ 0xで解決される)よりも一般的なことについて話していると仮定し、例としてのみベクトルを使用します。

むしろ、ある種のローカル関数を介して実行されることを望んでいます。

const vector<int> values = []{
    vector<int> v;
    copy(some_other_data.begin(), some_other_data.end(), v);
    sort(v);
    return v;
}();

(C ++ 0xの無名関数構文を台無しにする可能性があります)。これは、「ここで説明するルーチンに従ってconstベクトルを準備する」と非常に自然に読み取ることができます。かっこだけで少し気になります。

C ++ 0xがプログラマーにとってより自然になった後、このコードがどのようにC++イディオムになるかがわかります。

(dehmannの提案の後に編集)

于 2010-08-25T17:00:54.680 に答える
3

C++0x はブレース初期化子を使用してこれをある程度解決することは既に述べました。

const std::vector<int> values{1, 2, 3, 4, 5};

constただし、これは初期化のみを許可し、たとえば、コンストラクターの実行後に非メンバー関数を呼び出すことは許可しません。次のようにマクロを定義できconstifyます。

#define constify(type, id) \
for (type const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

次のように使用できます。

std::vector<int> v;

// v is non-const here.

constify (std::vector<int>, v) {

    // v is const here.

}

これは、次のステートメントまたはブロックを 1 回だけ実行するループを設定することで機能し、構成されたfor変数はループ本体に対してローカルになります。i_constlocal の前のヘルパー変数の宣言に注意してくださいi: ステートメントはそれ自体int const& i(i)(つまり、初期化iされていない値) に初期化され、に宣言された を代わりに参照する必要があるため、追加のレベルが必要です。(i)i

C++0x の機能を利用できる場合、decltypeキーワードを使用すると、次の呼び出しから型を省略できますconstify

#define constify(id) \
for (decltype(id) const& id##_const(id), & id(id##_const), \
    * constify_index = &id; constify_index; constify_index = 0)

簡単に書くことができます:

constify (v) {
    // ...
}

どちらのバージョンも、変数が最初に宣言されているかどうかにかかわらず機能しconstます。そうです、あなたが探していたものと非常によく似たものが確かに可能ですが、おそらくそれだけの価値はありません.

于 2010-08-25T16:49:58.350 に答える
3

私もこれについて考えたことがあります。しかし、私見ですが、それは多くの混乱を引き起こし、その利点を上回ります. 考えてみれば、C++ における constness の概念全体は、すでに十分に混乱しています。

あなたのアイデアは、「初期化後に変数を読み取り専用にするにはどうすればよいですか?」に要約されます。コンストラクターで初期化され、getter を提供するが setter を提供しないクラスのプライベート メンバーを変数にすることで、同じ効果を得ることができます。

于 2010-08-25T16:40:39.877 に答える
2

現在、constコンパイラが知っているかどうかは不明であるため、コンパイラはconst変数を変更しようとするプログラムを受け入れません。

演算子を作成したい場合は、実行時に変更できるようにconstify、これを変数のプロパティにする必要があります (追加のキーワードなしで、すべての変数の)。もちろん、プログラムが (現在の) 変数を変更しようとするたびに例外をスローする必要があります。constつまり、すべての変数へのすべての書き込みアクセスはconst最初にプロパティをチェックする必要があります。

これはすべて、C++ およびその他すべての静的型付け言語の哲学に反します。また、既存のライブラリとのバイナリ互換性も損なわれます。

于 2010-08-25T18:18:01.870 に答える
2

次のビットを検討してください。

void foo(std::vector<int> & v) 
{
  v.push_back(1);
  constify v;
}
void bar() {
  std::vector<int> test(7);
  foo(test);
  test.clear();
}

vfooの変数は構成されていますか? testbarと同じ変数です。したがって、test.clear()呼び出しは無効である必要があります。あなたが本当に意味したのは、名前が変数ではなく「構成された」ということだと思います。

指定して実装するのは実際には簡単です: constify x;x という名前の const 参照の宣言であり、それが非表示にする変数 x と同じ基本型を持ちます。x前の宣言と同じスコープで定義できることを除いて、通常のスコープ規則に従います。

于 2010-08-26T08:59:04.443 に答える
0

ベクトルをクラスでラップし、ラップされたベクトルを可変であると宣言してから、ラッパーのconstインスタンスを作成できます。ラッピングクラスはベクトルを変更できますが、外部の呼び出し元にはconstオブジェクトが表示されます

于 2010-08-25T17:02:13.947 に答える