Reputation: 3907
I'm having a little trouble figuring out exactly how const applies in a specific case. Here's the code I have:
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?
}
Basically, I have a class called test. It uses four widgets internally, and I need it to return these, but if test was declared const, I want the widgets returned const also.
Can someone explain to me why the code in main() compiles?
Thank you very much.
Upvotes: 8
Views: 1269
Reputation:
The original query was how to return the WidgetHolder as const if the containing class was const. C++ uses const as part of the function signature and therefore you can have const and none const versions of the same function. The none const one is called when the instance is none const, and the const one is called when the instance is const. Therefore a solution is to access the widgets in the widget holder by functions, rather than directly. I have create a more simple example below which I believe answers the original question.
#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;
}
In terms of the original code, I think it would be easier to have a WidgetHolder as a member of the class Test and then return either a const or none const reference to it, and make the Widgets private members of the holder, and provide a const and none const accessor for each Widget.
class WidgetHolder {
...
Widget& GetA();
const Widget& GetA() const;
...
};
And then on the main class
class Test {
...
WigetHolder& AccessWidgets() { return m_Widgets;}
const WidgetHolder&AcessWidgets() const { return m_Widgets;}
private:
WidgetHolder m_Widgets;
...
};
Upvotes: 0
Reputation: 17725
EDIT: he deleted his answer, making me look a bit foolish :)
The answer by Flame is dangerously wrong. His WidgetHolder takes a reference to a value object in the constructor. As soon as the constructor returns, that passed-by-value object will be destroyed and so you'll hold a reference to a destroyed object.
A very simple sample app using his code clearly shows this:
#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;
}
The output would be something like
widget 0xbffff7f8 destroyed widget 0xbffff7f8 used widget 0xbffff7f4 destroyed
To avoid this the WidgetHolder constructor should take references to the variables it wants to store as references.
struct WidgetHolder { Widget& A; public: WidgetHolder(Widget & a): A(a) {} /* ... */ };
Upvotes: 0
Reputation: 71053
Your WidgetHolder
is going to hold invalid references (pointers). You are passing objects on the stack to the constructor and then holding references to their (temporary) addresses. This is guaranteed to break.
You should only assign references to objects with the same (or greater) lifetime as the reference itself.
Pass references to the constructor if you must hold references. Even better, don't hold the references at all and just make the copies.
Upvotes: 3
Reputation: 36459
You need to create a new type specifically for holding const Widget& objects. Ie:
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);
}
You will now get the following error (in gcc 4.3):
widget.cc: In function 'int main()': widget.cc:51: error: assignment of data-member 'Widget::x' in read-only structure
A similar idiom is used in the standard library with iterators ie:
class vector {
iterator begin();
const_iterator begin() const;
Upvotes: 7
Reputation: 994817
This compiles because although the WidgetHolder is a const object, this const-ness does not automatically apply to objects pointed to (referenced by) the WidgetHolder. Think of it at a machine level - if the WidgetHolder object itself were held in read-only memory, you could still write to things that were pointed to by the WidgetHolder.
The problem appears to lie in this line:
WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){}
As Frank mentioned, your references inside the WidgetHolder class are going to hold invalid references after the constructor returns. Therefore, you should change this to:
WidgetHolder(Widget &a, Widget &b, Widget &c, Widget &d): A(a), B(b), C(c), D(d){}
After you do that, it won't compile, and I leave it as an exercise for the reader to work out the rest of the solution.
Upvotes: 2
Reputation: 347586
unchangeable.AccessWidgets():
At this point, you are creating a new object of type WidgetHolder. This object is not protected by const.
You are also creating new widgets in the WidgetHolder and not references to the Wdiget.
Upvotes: 3