Pregunta

Tengo algunos problemas para descubrir exactamente cómo se aplica const en un caso específico.Aquí está el código que tengo:

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ó constante, quiero que los widgets también se devuelvan constantes.

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

Muchas gracias.

¿Fue útil?

Solución

Esto se compila porque, aunque WidgetHolder es un objeto constante, esta constancia no se aplica automáticamente a los objetos apuntados (a los que hace referencia) WidgetHolder.Piénselo a nivel de máquina: si el objeto WidgetHolder en sí se mantuviera en la memoria de solo lectura, aún podría escribir en las cosas a las que apunta el WidgetHolder.

El problema parece estar en esta línea:

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

Como mencionó Frank, sus referencias dentro de la clase WidgetHolder contendrán referencias no válidas después de que regrese el constructor.Por lo tanto, deberías cambiar esto 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 que el lector resuelva el resto de la solución.

Otros consejos

Debe crear un nuevo tipo específicamente para contener objetos Widget& constantes.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 obtendrá 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

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


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

inmutable.AccessWidgets():

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

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

Su WidgetHolder va a contener referencias no válidas (punteros).Estás pasando objetos en la pila al constructor y luego manteniendo referencias a sus direcciones (temporales).Está garantizado que esto se romperá.

Sólo debe asignar referencias a objetos con la misma (o mayor) vida útil que la propia referencia.

Pase referencias al constructor si debe contener referencias.Aún mejor, no guardes ninguna referencia y simplemente haz las copias.

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 por lo tanto mantendrás una referencia a un objeto destruido.

Una aplicación de muestra muy simple que usa 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 como

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

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

struct WidgetHolder
{
    Widget& A;

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

  /* ... */

};

La consulta original era cómo devolver el WidgetHolder como constante si la clase que lo contiene era constante.C++ usa const como parte de la firma de la función y, por lo tanto, puede tener versiones const y none const de la misma función.El none const se llama cuando la instancia es none const, y el const se llama cuando la instancia es constante.Por lo tanto, una solución es acceder a los widgets en el soporte de widgets mediante funciones, en lugar de hacerlo directamente.He creado un ejemplo más simple a continuación que creo que responde a 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 un WidgetHolder como miembro de la clase Test y luego devolverle una referencia constante o ninguna constante, hacer que los Widgets sean miembros privados del titular y proporcionar un const y none acceso constante 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;
...
};
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top