const メンバーと代入演算子。未定義の動作を回避するにはどうすればよいでしょうか? 質問する

const メンバーと代入演算子。未定義の動作を回避するにはどうすればよいでしょうか? 質問する

答えたについての質問std::vector オブジェクトと const の正確性、未定義の動作についてのコメントを受け取りました。私は同意できないので、質問があります。

const メンバーを持つクラスを考えてみましょう:

class A { 
public: 
    const int c; // must not be modified! 
    A(int c) : c(c) {} 
    A(const A& copy) : c(copy.c) { }     
    // No assignment operator
}; 

代入演算子を使いたいのですが、const_cast回答の 1 つにある次のコードのようには使いたくありません。

A& operator=(const A& assign) 
{ 
    *const_cast<int*> (&c)= assign.c;  // very very bad, IMHO, it is undefined behavior
    return *this; 
} 

私の解決策は

// Custom-defined assignment operator
A& operator=(const A& right)  
{  
    if (this == &right) return *this;  

    // manually call the destructor of the old left-side object
    // (`this`) in the assignment operation to clean it up
    this->~A(); 
    // use "placement new" syntax to copy-construct a new `A` 
    // object from `right` into left (at address `this`)
    new (this) A(right); 
    return *this;  
}  

未定義の動作 (UB) がありますか?

UB なしの解決策は何でしょうか?

ベストアンサー1

コードによって未定義の動作が発生します。

「A が基本クラスとして使用され、これ、あれ、またはその他の場合は未定義」というだけではありません。実際には常に未定義です。は、新しいオブジェクトを参照することが保証されていないreturn *thisため、すでに UB です。this

具体的には、3.8/7 を検討してください。

オブジェクトの有効期間が終了した後、オブジェクトが占有していたストレージが再利用または解放される前に、元のオブジェクトが占有していたストレージの場所に新しいオブジェクトが作成されると、元のオブジェクトを指していたポインター、元のオブジェクトを参照していた参照、または元のオブジェクトの名前は自動的に新しいオブジェクトを参照し、新しいオブジェクトの有効期間が開始されると、次の条件を満たす場合に、新しいオブジェクトを操作するために使用できます。

...

— 元のオブジェクトの型はconst修飾されておらず、クラス型の場合は、型がconst修飾された非静的データメンバーまたは参照型を含まない。

ここで、「オブジェクトの有効期間が終了し、オブジェクトが占有していたストレージが再利用または解放される前に、元のオブジェクトが占有していたストレージの場所に新しいオブジェクトが作成される」というのが、まさに実行していることです。

オブジェクトはクラス型であり、する型がconst修飾された非静的データメンバーが含まれています。したがって、代入演算子が実行された後、古いオブジェクトを参照するポインタ、参照、および名前はない新しいオブジェクトを参照し、それを操作するために使用できることが保証されます。

何が問題になるかの具体的な例として、次のことを考慮してください。

A x(1);
B y(2);
std::cout << x.c << "\n";
x = y;
std::cout << x.c << "\n";

この出力を期待しますか?

1
2

違います! そのような出力が得られる可能性はありますが、const メンバーが 3.8/7 で述べられているルールの例外となっている理由は、コンパイラがx.cそれを const オブジェクトとして扱うことができるためです。言い換えると、コンパイラはこのコードを次のように扱うことができます。

A x(1);
B y(2);
int tmp = x.c
std::cout << tmp << "\n";
x = y;
std::cout << tmp << "\n";

なぜなら(非公式に)constオブジェクトは値を変更しません. この保証の潜在的な価値は、constオブジェクトを含むコードを最適化するときに明らかです。x.c それなしUB を呼び出す場合、この保証は削除する必要があります。したがって、標準の作成者がエラーなしで作業を完了している限り、必要なことを行う方法はありません。

[*] 実際、this配置 new の引数として使用することについては疑問があります。おそらく、それを最初にコピーしてvoid*、それを使用するべきだったのでしょう。しかし、関数全体を保存しないので、それが具体的に UB であるかどうかは気になりません。

おすすめ記事