У меня небольшие проблемы с определением того, как именно 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. Он использует четыре виджета внутри, и мне нужно, чтобы он возвращал их, но если test был объявлен как const, я хочу, чтобы виджеты также возвращали const.
Может кто-нибудь объяснить мне, почему код в main () компилируется?
Большое спасибо.





unchangeable.AccessWidgets ():
На этом этапе вы создаете новый объект типа WidgetHolder. Этот объект не защищен константой.
Вы также создаете новые виджеты в WidgetHolder, а не ссылки на Wdiget.
Патрик, ты ошибаешься. Конструктор создает новые виджеты, а затем ссылки являются ссылками на эти новые виджеты. Он не получает ссылок на свои оригинальные виджеты.
Ваш WidgetHolder будет содержать недопустимые ссылки (указатели). Вы передаете объекты в стеке конструктору, а затем сохраняете ссылки на их (временные) адреса. Это гарантированно сломается.
Вы должны назначать ссылки только на объекты с таким же (или большим) временем жизни, что и сама ссылка.
Передайте ссылки в конструктор, если вы должны содержать ссылки. Более того, вообще не храните ссылки и просто делайте копии.
В качестве альтернативы передача указателей (не ссылок) допустима, если вы даете явные гарантии времени жизни и владения этими объектами. Если вы передаете ссылки, WidgetHolder не сможет «владеть» объектами.
Это компилируется, потому что, хотя WidgetHolder является константным объектом, эта константа не применяется автоматически к объектам, на которые указывает (ссылается) WidgetHolder. Подумайте об этом на уровне машины - если бы сам объект WidgetHolder находился в постоянной памяти, вы все равно могли бы писать в то, на что указывал WidgetHolder.
Проблема, похоже, заключается в этой строке:
WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){}
Как упоминал Фрэнк, ваши ссылки внутри класса WidgetHolder будут содержать недопустимые ссылки после возврата конструктора. Следовательно, вы должны изменить это на:
WidgetHolder(Widget &a, Widget &b, Widget &c, Widget &d): A(a), B(b), C(c), D(d){}
После того, как вы это сделаете, он не будет компилироваться, и я оставляю его читателю в качестве упражнения для выработки остальной части решения.
Вам нужно создать новый тип специально для хранения константных виджетов и объектов. Т.е.:
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;
Обновлено: он удалил свой ответ, из-за чего я выгляжу немного глупо :)
Ответ 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) {}
/* ... */
};
Исходный запрос заключался в том, как вернуть WidgetHolder как const, если содержащий класс был const. C++ использует константу как часть сигнатуры функции, поэтому вы можете иметь константные и никакие константные версии одной и той же функции. Не константный вызывается, когда экземпляр не является константой, а константный вызывается, когда экземпляр константный. Поэтому решение состоит в том, чтобы получить доступ к виджетам в держателе виджетов с помощью функций, а не напрямую. Ниже я создал более простой пример, который, как мне кажется, отвечает на исходный вопрос.
#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 аксессор для каждого виджета.
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;
...
};
Новые виджеты не создаются в WidgetHolder; члены являются ссылками, а аргументы конструктора - копиями.