17

const_cast が悪である理由を実際に見つけることができない間、私はこの声明を聞き続けています。

次の例では:

template <typename T>
void OscillatorToFieldTransformer<T>::setOscillator(const SysOscillatorBase<T> &src)
{
    oscillatorSrc = const_cast<SysOscillatorBase<T>*>(&src);
}

私は参照を使用しており、const を使用することで、参照が変更されないように保護しています。一方、const_cast を使用しないと、コードはコンパイルされません。ここで const_cast が悪いのはなぜですか?

同じことが次の例にも当てはまります。

template <typename T>
void SysSystemBase<T>::addOscillator(const SysOscillatorBase<T> &src)
{
    bool alreadyThere = 0;
    for(unsigned long i = 0; i < oscillators.size(); i++)
    {
        if(&src == oscillators[i])
        {
            alreadyThere = 1;
            break;
        }
    }
    if(!alreadyThere)
    {
        oscillators.push_back(const_cast<SysOscillatorBase<T>*>(&src));
    }
}

const_cast を使用するのがいかに悪い考え/非専門的であるかを確認できる例をいくつか教えてください。

努力していただきありがとうございます:)

4

9 に答える 9

36

引数 を変更できないようにするという の目的を妨害しているためconstです。したがって、何かの性質を捨てると、無意味でコードが肥大化し、引数を変更しないという関数のユーザーに対する約束を破ることになります。const

さらに、 を使用const_castすると、未定義の動作が発生する可能性があります。次のコードを検討してください。

SysOscillatorBase<int> src;
const SysOscillatorBase<int> src2;

...

aFieldTransformer.setOscillator(src);
aFieldTransformer.setOscillator(src2);

最初の電話では、すべて順調です。constオブジェクトの本質ではない性質を捨てて、constうまく修正することができます。ただし、2 番目の呼び出しでは、真のオブジェクトの性質をsetOscillator捨てています。どこかでそのオブジェクトを変更し場合、実際には. マークされたオブジェクトが実際に宣言された場所にあるかどうかはわからないため、オブジェクトを決して変更しないことが確実でない限り、絶対に使用しないでください。そして、あなたがそうしないなら、ポイントは何ですか?constconstconstconst constconst_cast

サンプル コードでは、constである可能性のあるオブジェクトへの非ポインターを格納しています。constこれは、オブジェクトを変更するつもりであることを示しています (それ以外の場合は、単にポインターを格納しないのはなぜconstですか?)。これにより、未定義の動作が発生する可能性があります。

また、そのようにすると、関数に一時的なものを渡すことができます。

blah.setOscillator(SysOscillatorBase<int>()); // compiles

そして、関数が1を返すと無効になる一時へのポインターを格納しています。const非参照を取る場合、この問題はありません。

一方、const_cast を使用しないと、コードはコンパイルされません。

次に、コードを変更します。キャストを追加して機能させないでください。コンパイラは何らかの理由でそれをコンパイルしていません。理由がわかったので、ペグに合わせて四角い穴を丸い穴にキャストする代わりに、vectorポインターをホールドすることができます。const

constしたがって、代わりに非参照を受け入れるメソッドを使用する方が良いでしょう。使用するconst_castことはほとんど良い考えではありません。


1関数が呼び出された式が実際に終了したとき。

于 2012-05-08T12:11:17.863 に答える
11

const を使用することで、参照が変更されないように保護しています

参照は変更できません。一度初期化すると、常に同じオブジェクトを参照します。参照であるconstということは、それが参照するオブジェクトを変更できないことを意味します。しかしconst_cast、そのアサーションを元に戻し、オブジェクトを変更できるようにします。

一方、const_cast を使用しないと、コードはコンパイルされません。

これは何の正当化にもなりません。constC++ は、オブジェクトの変更を許可する可能性のあるコードをコンパイルすることを拒否しますconst。そのようなプログラムは正しくありません。const_cast正しくないプログラムをコンパイルする手段です — それが問題です。

たとえば、プログラムでは、オブジェクトがあるように見えます

std::vector< SysOscillatorBase<T> * > oscillators

このことを考慮:

Oscillator o; // Create this object and obtain ownership
addOscillator( o ); // cannot modify o because argument is const
// ... other code ...
oscillators.back()->setFrequency( 3000 ); // woops, changed someone else's osc.

const 参照によってオブジェクトを渡すということは、呼び出された関数がそれを変更できないだけでなく、関数がそれを変更できる他の誰かに渡すことができないことを意味します。const_castそれに違反します。

C++ の強みは、所有権と値のセマンティクスに関することを保証するツールを提供することです。これらのツールを無効にしてプログラムをコンパイルすると、バグが有効になります。優れたプログラマーは、それが受け入れられるとは思いません。

この特定の問題の解決策として、vectorオブジェクト (または使用しているコンテナー) は、ポインターではなく値でオブジェクトを格納する必要があるようです。次にaddOscillator、const 参照を受け入れることができますが、格納されたオブジェクトは変更可能です。さらに、コンテナーはオブジェクトを所有し、それらが安全に削除されることを保証します。

于 2012-05-08T12:22:04.550 に答える
9

インターフェイスに非定数ポインタ/参照があるが、実装が引数を変更しない(古いconst_cast)ライブラリに適応する以外の理由での使用は間違っており、危険です。

それが間違っている理由は、インターフェイスが定数オブジェクトへの参照またはポインタを取得するときに、オブジェクトを変更しないことを約束しているためです。他のコードは、オブジェクトを変更しないことに依存する場合があります。たとえば、コピーするのに費用のかかるメンバーを保持し、それと一緒に他のいくつかの不変条件を保持するタイプを考えてみます。

vector<double>と事前に計算された平均値を考えてみましょう。*averageは、クラスインターフェイスを介して新しい要素が追加されるたびに更新されます。これは、更新が安価であり、頻繁に要求される場合は、毎回データから再計算する必要がないためです。ベクターのコピーにはコストがかかりますが、読み取りアクセスが必要になる可能性があるため、この型は、コンテナーに既に存在する値をチェックするためのforユーザーコードを返す安価なアクセサーを提供できます。std::vector<double> const &これで、ユーザーコードが参照の定数を破棄してベクトルを更新すると、クラスが平均を保持する不変条件が壊れ、プログラムの動作が不正確になります。

また、渡されたオブジェクトが実際に変更可能であるかどうかの保証がないため、危険です。Cのnullで終了する文字列を受け取り、それを大文字に変換する単純な関数を考えてみましょう。

void upper_case( char * p ) {
   while (*p) {
     *p = ::to_upper(*p);
      ++p;
   }
}

const char*ここで、インターフェースを変更して。を取得し、実装を変更してを削除することにしたと仮定しますconst。古いバージョンで機能していたユーザーコードは新しいバージョンでも機能しますが、古いバージョンでエラーとしてフラグが立てられるコードの一部は、コンパイル時に検出されなくなります。誰かがと同じくらい愚かなことをすることに決めたと考えてくださいupper_case( typeid(int).name() )。ここで問題となるのは、の結果が変更可能なオブジェクトへtypeidの定数参照だけでなく、定数オブジェクトへの参照になることです。コンパイラはオブジェクトを読み取り専用セグメントに自由に格納し、ローダーはオブジェクトをメモリの読み取り専用ページにロードできます。変更しようとすると、プログラムがクラッシュします。type_info

const_castどちらの場合も、余分な不変条件が維持されているかどうか(ケース1)、またはオブジェクトが実際に一定であるかどうか(ケース2)をコンテキストから知ることはできないことに注意してください。

反対に、存在する理由は、キーワードconst_castをサポートしていない古いCコードに適応することでした。関数がオブジェクトを変更しないことがわかっていて文書化されていても、constしばらくの間、のような関数strlenはを取ります。char*その場合、 const-nessを変更するのではなく、タイプconst_cast適合させるために使用するのが安全です。Cはすでに非常に長い間サポートされており、適切な使用法が少ないことに注意してください。constconst_cast

于 2012-05-08T12:27:01.663 に答える
3

、 const_cast が悪である理由を実際に見つけることはできません。

責任を持って使用し、自分が何をしているのかを知っている場合は、そうではありません。(または、 const 修飾子だけが異なるすべてのメソッドのコードを真剣にコピーして貼り付けますか?)

ただし、 const_cast の問題は、もともとconst だった変数で使用すると、未定義の動作を引き起こす可能性があることです。つまり、const 変数を宣言する場合は、const_cast して変更を試みます。そして、未定義の動作は良いことではありません。

あなたの例には、まさにこの状況が含まれています。おそらく const 変数が非 const に変換されています。問題を回避するには、const SysOscillatorBase<T>*(const ポインター) またはSysOscillatorBase<T>(コピー) をオブジェクト リストに格納するか、const 参照の代わりに参照を渡します。

于 2012-05-08T12:20:00.657 に答える
3

const_castメソッドで指定された契約、つまり「私は変更しない」を破ることができるので、これは悪いことですsrc。呼び出し元は、メソッドがそれに固執することを期待しています。

于 2012-05-08T12:12:53.873 に答える
3

少なくとも問題です。2 つの constness を区別する必要があります。

  • インスタンス化された変数の constness
    これにより、物理的な constness が発生する可能性があり、データは読み取り専用セグメントに配置されます

  • constness of reference parameter / pointer
    これは論理的な constness であり、コンパイラによってのみ強制されます

const が物理的に const でない場合にのみ、const をキャストすることが許可されており、パラメーターからそれを判断することはできません。

さらに、コードの一部が const に正しく、他の部分が正しくないというのは「におい」です。これは、避けられない場合があります。


最初の例では、非 const ポインターであると想定するものに const 参照を割り当てます。これにより、少なくとも const キャストが必要な元のオブジェクトを変更できます。説明する:

SysOscillatorBase<int> a;
const SysOscillatorBase<int> b;

obj.setOscillator(a); // ok, a --> const a --> casting away the const
obj.setOscilaltor(b); // NOT OK: casting away the const-ness of a const instance

2番目の例にも同じことが当てはまります。

于 2012-05-08T12:17:10.763 に答える
2

コーディング契約に違反しています。値を const としてマークすることは、この値を使用できるが決して変更しないことを意味します。const_cast はこの約束を破り、予期しない動作を引き起こす可能性があります。

あなたが与える例では、コードが正しくないようです。oscillatorSrc はおそらく const ポインターである必要がありますが、実際に値を変更する必要がある場合は、それを const として渡すべきではありません。

于 2012-05-08T12:13:15.683 に答える
1

基本的に const は、値を変更しないことをユーザーとコンパイラーに約束します。値を変更しないことが知られている C ライブラリ関数 (const が存在しない場所) を使用するときにのみ使用する必要があります。

bool compareThatShouldntChangeValue(const int* a, const int* b){
    int * c = const_cast<int*>(a);
    *c = 7;
    return a == b;
}

int main(){
   if(compareThatShouldntChangeValue(1, 7)){
      doSomething();
   }
}
于 2012-05-08T12:12:42.047 に答える
0

おそらく、コンテナをconstオブジェクトを含むものとして定義する必要があります

template <typename T> struct Foo {
    typedef std::vector<SysOscillator<T> const *>  ossilator_vector;
}

Foo::ossilator_vector<int> oscillators;


// This will compile
SysOscillator<int> const * x = new SysOscillator<int>();
oscillators.push_back(x);

// This will not
SysOscillator<int> * x = new SysOscillator<int>();
oscillators.push_back(x);

そうは言っても、コンテナの typedef を制御できない場合は、コードとライブラリの間のインターフェイスで const_cast を使用しても問題ないかもしれません。

于 2012-05-08T12:18:37.253 に答える