10

C++ 標準では、最初に宣言されたオブジェクトを変更することconstは未定義の動作であると述べています。では、コンストラクタとデストラクタはどのように動作するのでしょうか?

class Class {
public:
    Class() { Change(); }
    ~Class() { Change(); }
    void Change() { data = 0; }
private:
    int data;
};

//later:
const Class object;
//object.Change(); - won't compile
const_cast<Class&>( object ).Change();// compiles, but it's undefined behavior

ここでは、コンストラクターとデストラクタは呼び出し元のコードとまったく同じことを行いますが、オブジェクトを変更することは許可されており、呼び出し元は許可されていません。未定義の動作に遭遇します。

実装の下で、標準に従ってどのように機能するはずですか?

4

5 に答える 5

16

標準では、コンストラクタとデストラクタがconstオブジェクトを処理することを明示的に許可しています。12.1/4「コンストラクター」から:

constvolatileまたはconst volatileオブジェクトに対してコンストラクターを呼び出すことができます。...constおよびvolatileセマンティクス (7.1.5.1) は、構築中のオブジェクトには適用されません。このようなセマンティクスは、最も派生したオブジェクト (1.8) のコンストラクターが終了したときにのみ有効になります。

そして12.4/2の「デストラクタ」:

constvolatileまたはconst volatileオブジェクトに対してデストラクタを呼び出すことができます。... constおよびvolatileセマンティクス (7.1.5.1) は、破棄中のオブジェクトには適用されません。このようなセマンティクスは、最も派生したオブジェクト (1.8) のデストラクタが開始されると、有効になりません。

背景として、Stroustrup は "Design and Evolution of C++" (13.3.2 Refinement of the Definition of const) で次のように述べています。

すべてではなく一部のconstオブジェクトを読み取り専用メモリ (ROM) に配置できるようにするために、コンストラクターを持つ (つまり、ランタイムの初期化が必要な) オブジェクトはすべて ROM に配置できないという規則を採用しました。constオブジェクトはできます。

...

宣言されたオブジェクトはconst、コンストラクタの完了からデストラクタの開始まで不変であると見なされます。これらのポイント間のオブジェクトへの書き込みの結果は、未定義と見なされます。

最初に を設計したとき、コンストラクターが実行されるまでは書き込み可能であり、ハードウェア マジックによって読み取り専用になり、最終的にデストラクタに入ると再び書き込み可能になるオブジェクトがconst理想であると主張したことを覚えています。const実際にこのように機能するタグ付きアーキテクチャを想像することができます。そのような実装は、誰かが定義されたオブジェクトに書き込むことができる場合、実行時エラーを引き起こしますconst。一方、誰かが参照またはポインタconstとして渡された定義されていないオブジェクトに書き込むことができました。constどちらの場合も、ユーザーはconst最初にキャスト アウェイする必要があります。このビューの意味は、const最初に定義されたオブジェクトをキャストすることです。const元々定義されていなかったオブジェクトに対して同じことを行うことconstは合法であり、明確に定義されています。

このルールの改良ではconst、型にコンストラクターがあるかどうかに関係なく、の意味が異なることに注意してください。原則として、それらはすべてそうです。現在宣言されているオブジェクトはconst、初期値を受け取った後に変更されないようにするために、ROM に配置したり、コード セグメントに配置したり、アクセス制御によって保護したりできます。ただし、現在のシステムでは一般にconst、すべての形式の破損からすべてを保護することはできないため、このような保護は必要ありません。

于 2010-02-16T06:33:14.277 に答える
2

Jerry Coffin の発言を詳しく説明すると、標準では const オブジェクトへのアクセスが未定義になりますが、そのアクセスがオブジェクトの存続期間中に発生した場合に限られます。

7.1.5.1/4:

ミュータブル (7.1.1) と宣言されたクラス メンバーを変更できることを除いて、その有効期間 (3.8) 中に const オブジェクトを変更しようとすると、未定義の動作が発生します。

オブジェクトの有効期間は、コンストラクターが終了した後にのみ開始されます。

3.8/1:

型 T のオブジェクトの有効期間は、次の時点で始まります。

  • タイプ T に適した位置合わせとサイズのストレージが取得されます。
  • T が非自明なコンストラクタ (12.1) を持つクラス型である場合、コンストラクタの呼び出しは完了しています。
于 2010-02-16T09:16:24.187 に答える
1

標準は、実装がどのように機能するかについて実際には多くを述べていませんが、基本的な考え方は非常に単純constです。ctor はオブジェクトを作成するものの一部であるため、ctor が返されるまで (しばらくしてから) オブジェクトは実際にはオブジェクトではありません。同様に、dtor はオブジェクトの破棄に関与するため、実際には完全なオブジェクトを操作しているわけではありません。

于 2010-02-16T06:35:54.743 に答える
1

標準を無視すると、誤った動作につながる可能性がある方法を次に示します。次のような状況を考えてみましょう:

class Value
{
    int value;

public: 
    value(int initial_value = 0)
        : value(initial_value)
    {
    }

    void set(int new_value)
    {
        value = new_value;
    }

    int get() const
    {
        return value;
    }
}

void cheat(const Value &v);

int doit()
{
    const Value v(5);

    cheat(v);

    return v.get();
}

最適化されている場合、コンパイラは v が const であることを認識しているため、への呼び出しを に置き換えることができv.get()ます5

しかし、別の翻訳単位で、次のように定義したとしましょうcheat():

void cheat(const Value &cv)
{
     Value &v = const_cast<Value &>(cv);
     v.set(v.get() + 2);
}

そのため、ほとんどのプラットフォームでこれが実行されますが、オプティマイザの動作によって動作が変わる可能性があります。

于 2010-02-16T08:10:47.390 に答える
0

ユーザー定義型の constness は、組み込み型の constness とは異なります。ユーザー定義型で使用される場合の constness は、「論理 constness」と呼ばれます。コンパイラは、「const」と宣言されたメンバー関数のみを const オブジェクト (またはポインター、または参照) で呼び出すことができるように強制します。非 constメンバー関数はオブジェクトの状態を変更できる必要があるため (メンバー変数が宣言されている場合は、constメンバー関数でさえ変更できる必要があるため)、コンパイラは読み取り専用メモリ領域にオブジェクトを割り当てることができませんmutable

組み込み型の場合、コンパイラはオブジェクトを読み取り専用メモリに割り当てることが許可されていると思います(プラットフォームがそのようなことをサポートしている場合)。したがって、const をキャストして変数を変更すると、ランタイム メモリ保護違反が発生する可能性があります。

于 2010-02-16T06:34:52.960 に答える