質問
特定のケースで const がどのように適用されるかを正確に理解するのに少し苦労しています。私が持っているコードは次のとおりです。
struct Widget
{
Widget():x(0), y(0), z(0){}
int x, y, z;
};
struct WidgetHolder //Just a simple struct to hold four Widgets.
{
WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){}
Widget& A;
Widget& B;
Widget& C;
Widget& D;
};
class Test //This class uses four widgets internally, and must provide access to them externally.
{
public:
const WidgetHolder AccessWidgets() const
{
//This should return our four widgets, but I don't want anyone messing with them.
return WidgetHolder(A, B, C, D);
}
WidgetHolder AccessWidgets()
{
//This should return our four widgets, I don't care if they get changed.
return WidgetHolder(A, B, C, D);
}
private:
Widget A, B, C, D;
};
int main()
{
const Test unchangeable;
unchangeable.AccessWidgets().A.x = 1; //Why does this compile, shouldn't the Widget& be const?
}
基本的に、testというクラスがあります。内部的には 4 つのウィジェットを使用しており、これらを返す必要がありますが、test が const として宣言されている場合は、ウィジェットも const で返されるようにしたいと考えています。
main() のコードがコンパイルされる理由を誰かが説明してもらえますか?
どうもありがとうございます。
解決
WidgetHolder は const オブジェクトですが、この const 性は WidgetHolder が指す (参照する) オブジェクトには自動的に適用されないため、これはコンパイルされます。これをマシン レベルで考えてください。WidgetHolder オブジェクト自体が読み取り専用メモリに保持されている場合でも、WidgetHolder が指すものに書き込むことができます。
問題は次の行にあるようです。
WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){}
Frank が述べたように、WidgetHolder クラス内の参照は、コンストラクターが返された後、無効な参照を保持することになります。したがって、これを次のように変更する必要があります。
WidgetHolder(Widget &a, Widget &b, Widget &c, Widget &d): A(a), B(b), C(c), D(d){}
これを実行してもコンパイルされません。読者が残りの解決策を見つけるための演習として残しておきます。
他のヒント
const Widget& オブジェクトを保持するための新しい型を作成する必要があります。つまり:
struct ConstWidgetHolder
{
ConstWidgetHolder(const Widget &a, const Widget &b, const Widget &c, const Widget &d): A(a), B(b), C(c), D(d){}
const Widget& A;
const Widget& B;
const Widget& C;
const Widget& D;
};
class Test
{
public:
ConstWidgetHolder AccessWidgets() const
{
return ConstWidgetHolder(A, B, C, D);
}
次のエラーが表示されます (gcc 4.3 の場合)。
widget.cc: In function 'int main()': widget.cc:51: error: assignment of data-member 'Widget::x' in read-only structure
同様のイディオムがイテレータを含む標準ライブラリで使用されます。つまり、次のようになります。
class vector {
iterator begin();
const_iterator begin() const;
unchangeable.AccessWidgets():
この時点で、WidgetHolder タイプの新しいオブジェクトを作成しています。このオブジェクトは const によって保護されません。
また、Wdiget への参照ではなく、WidgetHolder に新しいウィジェットを作成しています。
あなたの WidgetHolder
無効な参照 (ポインター) を保持します。スタック上のオブジェクトをコンストラクターに渡し、その (一時的な) アドレスへの参照を保持します。これは壊れることが保証されています。
参照は、参照自体と同じ (またはそれ以上) 存続期間を持つオブジェクトにのみ割り当てる必要があります。
参照を保持する必要がある場合は、コンストラクターに参照を渡します。さらに良いのは、参考文献をまったく保持せず、コピーするだけです。
編集:彼は答えを削除したので、私は少し愚かに見えました:)
Flame の答えは危険なほど間違っています。彼の WidgetHolder は、コンストラクター内の値オブジェクトへの参照を受け取ります。コンストラクターが戻るとすぐに、その値渡しオブジェクトは破棄されるため、破棄されたオブジェクトへの参照を保持することになります。
彼のコードを使用した非常に単純なサンプル アプリは、これを明確に示しています。
#include <iostream>
class Widget
{
int x;
public:
Widget(int inX) : x(inX){}
~Widget() {
std::cout << "widget " << static_cast< void*>(this) << " destroyed" << std::endl;
}
};
struct WidgetHolder
{
Widget& A;
public:
WidgetHolder(Widget a): A(a) {}
const Widget& a() const {
std::cout << "widget " << static_cast< void*>(&A) << " used" << std::endl;
return A;
}
};
int main(char** argv, int argc)
{
Widget test(7);
WidgetHolder holder(test);
Widget const & test2 = holder.a();
return 0;
}
出力は次のようになります
widget 0xbffff7f8 destroyed widget 0xbffff7f8 used widget 0xbffff7f4 destroyed
これを回避するには、WidgetHolder コンストラクターは、参照として保存したい変数への参照を取得する必要があります。
struct WidgetHolder { Widget& A; public: WidgetHolder(Widget & a): A(a) {} /* ... */ };
元のクエリは、含まれるクラスが const の場合に WidgetHolder を const として返す方法でした。C++ は関数シグネチャの一部として const を使用するため、同じ関数の const バージョンと none const バージョンを使用できます。none const はインスタンスが none const の場合に呼び出され、const はインスタンスが const の場合に呼び出されます。したがって、解決策は、直接ではなく関数によってウィジェット ホルダー内のウィジェットにアクセスすることです。元の質問に答えられると思われる、より単純な例を以下に作成しました。
#include <stdio.h>
class Test
{
public:
Test(int v){m_v = v;}
~Test(){printf("Destruct value = %d\n",m_v);}
int& GetV(){printf ("None Const returning %d\n",m_v); return m_v; }
const int& GetV() const { printf("Const returning %d\n",m_v); return m_v;}
private:
int m_v;
};
void main()
{
// A none const object (or reference) calls the none const functions
// in preference to the const
Test one(10);
int& x = one.GetV();
// We can change the member variable via the reference
x = 12;
const Test two(20);
// This will call the const version
two.GetV();
// So the below line will not compile
// int& xx = two.GetV();
// Where as this will compile
const int& xx = two.GetV();
// And then the below line will not compile
// xx = 3;
}
元のコードに関しては、WidgetHolder を Test クラスのメンバーとして持ち、それへの const または none const 参照を返し、Widget をホルダーのプライベート メンバーにして、各ウィジェットの const および none const アクセサー。
class WidgetHolder {
...
Widget& GetA();
const Widget& GetA() const;
...
};
そしてメインクラスで
class Test {
...
WigetHolder& AccessWidgets() { return m_Widgets;}
const WidgetHolder&AcessWidgets() const { return m_Widgets;}
private:
WidgetHolder m_Widgets;
...
};