2008-09-11 25 views
8

Tengo un pequeño problema para averiguar exactamente cómo se aplica la const en un caso específico. Aquí está el código que tengo:Const Struct &

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? 
} 

Básicamente, tengo una clase llamada prueba. Utiliza cuatro widgets internamente, y necesito que los devuelva, pero si la prueba se declaró const, quiero que los widgets vuelvan también a const.

¿Puede alguien explicarme por qué el código en main() se compila?

Muchas gracias.

Respuesta

2

Esto se compila porque aunque el WidgetHolder es un objeto const, esta const-ness no se aplica automáticamente a los objetos apuntados (referenciados por) WidgetHolder. Piénselo a nivel de máquina: si el objeto WidgetHolder se mantuviera en memoria de solo lectura, aún podría escribir en cosas apuntadas por WidgetHolder.

El problema parece radicar en esta línea:

WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){} 

Como se mencionó Frank, sus referencias dentro de la clase WidgetHolder van a mantener las referencias no válidas después de las declaraciones de constructor. Por lo tanto, debe cambiarlo a:

WidgetHolder(Widget &a, Widget &b, Widget &c, Widget &d): A(a), B(b), C(c), D(d){} 

Después de hacer eso, no se compilará, y lo dejo como ejercicio para el lector para trabajar el resto de la solución.

3

unchangeable.AccessWidgets():

En este punto, se crea un nuevo objeto de tipo WidgetHolder. Este objeto no está protegido por const.

También está creando nuevos widgets en el WidgetHolder y no referencias al Wdiget.

+0

nuevos widgets no se están creando en WidgetHolder; los miembros son referencias pero los argumentos del constructor son copias. –

+0

Patrick estás equivocado. El constructor está creando nuevos widgets y luego las referencias son referencias a esos nuevos widgets. Él no está recibiendo referencias a sus widgets originales. –

3

Su WidgetHolder va a contener referencias inválidas (punteros). Está pasando objetos en la pila al constructor y luego manteniendo referencias a sus direcciones (temporales). Esto está garantizado para romperse.

Solo debe asignar referencias a objetos con la misma (o mayor) duración que la referencia misma.

Pase referencias al constructor si debe contener referencias. Aún mejor, no mantenga las referencias en absoluto y solo haga las copias.

+0

Alternativamente, pasar punteros (no referencias) está bien si se otorgan garantías explícitas sobre la duración y propiedad de esos objetos. Si pasa referencias, no hay forma de que WidgetHolder "posea" los objetos. – hazzen

8

Debe crear un tipo nuevo específicamente para contener objetos const Widget &. Es decir:

 

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

Ahora va a obtener el siguiente error (en gcc 4.3):

 
widget.cc: In function 'int main()': 
widget.cc:51: error: assignment of data-member 'Widget::x' in read-only structure 

un lenguaje similar se utiliza en la biblioteca estándar con iteradores es decir:

 

class vector { 
    iterator begin(); 
    const_iterator begin() const; 
 
0

EDITAR : borró su respuesta, haciéndome parecer un poco tonto :)

La respuesta de Flame es peligrosamente errónea.Su WidgetHolder toma una referencia a un objeto de valor en el constructor. Tan pronto como el constructor regrese, ese objeto pasado por valor será destruido y usted tendrá una referencia a un objeto destruido.

Una aplicación de ejemplo muy simple usando su código muestra claramente esto:

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

La salida sería algo así como

 
widget 0xbffff7f8 destroyed 
widget 0xbffff7f8 used 
widget 0xbffff7f4 destroyed 

Para evitar esto, el constructor WidgetHolder debe tomar referencias a las variables que quiere para almacenar como referencias.

 
struct WidgetHolder 
{ 
    Widget& A; 

public: 
    WidgetHolder(Widget & a): A(a) {} 

    /* ... */ 

}; 
0

La consulta original era cómo devolver el WidgetHolder como const si la clase contenedora era const. C++ utiliza const como parte de la firma de la función y, por lo tanto, puede tener const y ninguna const versiones de la misma función. El none const se llama cuando la instancia no es const, y el const se llama cuando la instancia es const. Por lo tanto, una solución es acceder a los widgets en el titular del widget por funciones, en lugar de acceder directamente. He creado un ejemplo más simple debajo del cual creo que responde la pregunta original.

#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; 

} 

En términos del código original, creo que sería más fácil tener una WidgetHolder como miembro de la clase Test y luego regresar ya sea una constante o ninguna referencia constante a la misma, y ​​hacer que los miembros privados Widgets del titular, y proporcionar un acceso directo y ninguno const para cada Widget.

class WidgetHolder { 
... 

Widget& GetA(); 
const Widget& GetA() const; 
... 
}; 

Y luego en la clase principal

class Test { 
... 
WigetHolder& AccessWidgets() { return m_Widgets;} 
const WidgetHolder&AcessWidgets() const { return m_Widgets;} 

private: 
    WidgetHolder m_Widgets; 
... 
};