문제
특정 경우에 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개의 위젯을 사용하고 이를 반환하려면 위젯이 필요하지만 테스트가 const로 선언된 경우 위젯도 const를 반환하길 원합니다.
main()의 코드가 컴파일되는 이유를 누군가 설명해 줄 수 있나요?
매우 감사합니다.
해결책
이는 WidgetHolder가 const 개체임에도 불구하고 이 const-ness가 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;
변경할 수 없습니다.액세스 위젯():
이 시점에서는 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 버전이 있고 const 버전이 없을 수 있습니다.none const는 인스턴스가 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 참조를 반환하거나 const 참조를 반환하지 않고 Widgets를 홀더의 비공개 멤버로 만들고 다음을 제공하는 것이 더 쉬울 것이라고 생각합니다. const이며 각 위젯에 대한 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;
...
};