Pregunta

Actualmente estoy sufriendo un pedo cerebral.He hecho esto antes, pero no recuerdo la sintaxis exacta y no puedo ver el código que escribí porque estaba trabajando en otra empresa en ese momento.Tengo este arreglo:

class P
{
// stuff
};

class PW : public P
{
// more stuff
};

class PR : public P
{
// more stuff
};

class C
{
public:
    P GetP() const { return p; }    
private:
    P p;
};

// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

Ahora me gustaría hacer que P sea intercambiable con PW y PR (y así PW y PR puedan intercambiarse).Probablemente podría salirme con la mía, pero este cambio de código ha ocurrido varias veces solo en este módulo.Estoy bastante seguro de que es un operador, pero por mi vida no recuerdo qué.

¿Cómo puedo hacer que P sea intercambiable con PW y PR con una cantidad mínima de código?

Actualizar: Para dar un poco más de aclaración.P significa Proyecto y R y W significan Lector y Escritor respectivamente.Todo lo que tiene el lector es el código para cargar, sin variables, y el escritor tiene código para simplemente escribir.Debe estar separado porque las secciones de Lectura y Escritura tienen varias clases de administrador y cuadros de diálogo, lo cual no es de interés real para Proyectos, que es la manipulación de archivos de proyecto.

Actualizar: También necesito poder llamar a los métodos de P y PW.Entonces, si P tiene un método a() y PW como método de llamada b(), entonces podría:

PW p = c.GetP();
p.a();
p.b();

Básicamente es para hacer que la conversión sea transparente.

¿Fue útil?

Solución

Si desea que esta parte se compile:



// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

Debe poder construir/convertir una P en una PW o una PR.Necesitas hacer algo como esto:



class PW : public P
{
    PW(const P &);
// more stuff
};

class PR : public P
{
    PR(const P &);
// more stuff
};

¿O quisiste decir algo más como:


class P
{
    operator PW() const;
    operator PR() const;
// stuff
};

Otros consejos

Estás intentando forzar variables reales, en lugar de punteros.Hacer eso requeriría un yeso.Sin embargo, si su definición de clase fuera así:

class C

    {
        public: 
            P* GetP() const { return p; }
        private:
            P* p;
    }

Entonces, ya sea que p fuera un puntero a P, PW o PR, su función no cambiaría y cualquier función (virtual) llamada en el P* devuelta por la función usaría la implementación de P, PW o PR. dependiendo de cuál era el miembro p..

Supongo que la clave para recordar es la Principio de sustitución de Liskov.Dado que PW y PR son subclases de P, pueden tratarse como si fueran Ps.Sin embargo, las MP no pueden ser tratadas como RP, y viceversa.

En el código anterior, tienes lo opuesto a rebanar problema.

Lo que estás intentando hacer es asignar desde una P a una PW o PR que contenga más información que el objeto fuente.¿Cómo haces esto?Digamos que P solo tiene 3 variables miembro, pero PW tiene 12 miembros adicionales: ¿de dónde vienen los valores para estas cuando escribes? PW p = c.GetP()?

Si esta tarea realmente es válido, lo que realmente debería indicar algún tipo de rareza en el diseño, entonces implementaría PW::operator=(const P&) y PR::operator=(const P&), PW::PW(const P&) y PR::PR(const P&).Pero esa noche no dormí muy bien.

Esto hace algo sensato, dado que todo se pasa por valor.No estoy seguro si es en lo que estabas pensando.

class P
{
public:
    template <typename T>
    operator T() const
    {
        T t;
        static_cast<T&>(t) = *this;
        return t;
    }
};

Para que PW y PR sean utilizables a través de una P, es necesario utilizar referencias (o punteros).Entonces realmente no necesitas cambiar la interfaz de C para que devuelva una referencia.

El principal problema en el código antiguo era que estabas copiando una P en una PW o una PR.Esto no funcionará ya que PW y PR tienen potencialmente más información que P y, desde una perspectiva de tipo, un objeto de tipo P no es un PW ni un PR.Aunque PW y PR son ambos P.

Cambie el código a esto y se compilará:Si desea devolver diferentes objetos derivados de una clase P en tiempo de ejecución, entonces la clase C debe poder almacenar todos los tipos diferentes que espera y estar especializada en tiempo de ejecución.Entonces, en la clase siguiente te permito especializarte pasando un puntero a un objeto que será devuelto por referencia.Para asegurarme de que el objeto sea seguro para excepciones, he envuelto el puntero en un puntero inteligente.

class C
{
    public:
        C(std::auto_ptr<P> x):
            p(x)
        {
            if (p.get() == NULL) {throw BadInit;}
        }
        // Return a reference.
        P& GetP() const { return *p; }        
    private:
        // I use auto_ptr just as an example
        // there are many different valid ways to do this.
        // Once the object is correctly initialized p is always valid.
        std::auto_ptr<P> p;
};

// ...
P&  p = c.GetP( );                   // valid
PW& p = dynamic_cast<PW>(c.GetP( )); // valid  Throws exception if not PW
PR& p = dynamic_cast<PR>(c.GetP( )); // valid  Thorws exception if not PR
// ...

Quizás te refieres al transmisión_dinámica ¿operador?

No son completamente intercambiables.PW es una P.PR es una P.Pero P no es necesariamente un PW, ni necesariamente un PR.Puede utilizar static_cast para convertir punteros de PW * a P *, o de PR * a P *.No debes usar static_cast para convertir objetos reales a su superclase debido al "corte".MI.gramo.si lanzas un objeto de PW a P, el material extra en PW será "cortado".Tampoco puedes usar static_cast para transmitir de P* a PW*.Si realmente tiene que hacerlo, use Dynamic_cast, que verificará en tiempo de ejecución si el objeto es realmente de la subclase correcta y le dará un error de tiempo de ejecución si no lo es.

No estoy seguro de lo que quieres decir exactamente, pero ten paciencia.

Ya lo son.Simplemente llame a todo P y podrá fingir que PR y PW son P.Sin embargo, PR y PW siguen siendo diferentes.

Hacer que los tres sean equivalentes resultaría en problemas con el principio de liskov.Pero entonces, ¿por qué les darías nombres diferentes si son realmente equivalentes?

El segundo y el tercero no serían válidos porque es una conversión ascendente implícita, lo cual es algo peligroso en C++.Esto se debe a que la clase a la que está transmitiendo tiene más funcionalidad que la clase a la que se le está asignando, por lo que, a menos que la transmita usted mismo explícitamente, el compilador de C++ arrojará un error (al menos debería hacerlo).Por supuesto, esto simplifica un poco las cosas (puede usar RTTI para ciertas cosas que pueden estar relacionadas con lo que desea hacer de manera segura sin invocar la ira de la mala objetividad), pero la simplicidad siempre es una buena manera de abordar los problemas.

Por supuesto, como se indica en algunas otras soluciones, puede solucionar este problema, pero creo que antes de intentar solucionarlo, es posible que desee repensar el diseño.

Utilice una referencia o un puntero a P en lugar de un objeto:

class C
{
 public:
  P* GetP() const { return p; }
 private:
  P* p;
};

Esto permitirá que un PW* o un PR* se vinculen a C.p.Sin embargo, si necesita pasar de P a PW o PR, debe usar Dynamic_cast<PW*>(p), que devolverá la versión PW* de p, o NULL de p no es un PW* (por por ejemplo, porque es un PR*).Sin embargo, Dynamic_cast tiene algunos gastos generales y es mejor evitarlo si es posible (use virtuales).

También puede utilizar el operador typeid() para determinar el tipo de tiempo de ejecución de un objeto, pero tiene varios problemas, incluido el de que debe incluirlo y que no puede detectar derivaciones adicionales.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top