tags:

views:

409

answers:

6
+6  Q: 

Const Struct&

Hi everyone. 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.

+3  A: 

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.

Brian R. Bondy
New widgets are not being created in WidgetHolder; the members are references but the constructor arguments are copies.
Patrick Johnmeyer
Patrick you are wrong. The constructor is creating new widgets and then the references are references to those new widgets. He is not getting references to his original widgets.
Brian R. Bondy
+3  A: 

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.

Frank Krueger
Alternatively, passing pointers (not references) is fine if you give explicit guarantees on the lifetime and ownership of those objects. If you pass references, there is no way for WidgetHolder to "own" the objects.
hazzen
+1  A: 

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.

Greg Hewgill
+7  A: 
Greg Rogers
A: 
Pieter
A: 

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;
...
};