2011-02-16 11 views
9

Dada esta artificial ejemplo:C++ miembro-función de encadenamiento de tipos de retorno y las clases derivadas

struct point_2d { 
    point_2d& x(int n) { 
    x_ = n; 
    return *this; 
    } 

    point_2d& y(int n) { 
    y_ = n; 
    return *this; 
    } 

    int x_, y_; 
}; 

struct point_3d : point_2d { 
    point_3d& z(int n) { 
    z_ = n; 
    return *this; 
    } 

    int z_; 
}; 

int main() { 
    point_3d p; 
    p.x(0).y(0).z(0); // error: "point_2d" has no member named "z" 
    return 0; 
} 

la idea es utilizar "encadenamiento-función miembro" para ser capaz de llamar a más de una función miembro en una fila . (Hay muchos ejemplos de esto; el anterior es el más corto que pude pensar para hacer esta pregunta. Mi problema real es similar y se describe a continuación.)

El problema es que si una clase derivada se agrega su propio encadenamiento de funciones-miembros, pero usted llama primero a la función miembro de la clase base, obtiene una referencia de clase base que, por supuesto, no funcionará para llamar a la función miembro de una clase derivada.

¿Hay alguna forma inteligente de resolver este problema y aún así mantener la capacidad de realizar un encadenamiento de función de miembro?


el problema real

Mi real problema es que mi clase base es una excepción y mi clase derivada es una clase derivada de la excepción de base. Para aquellas clases también, quiero usar funciones miembro de encadenamiento:

class base_exception : public std::exception { 
    // ... 
    base_exception& set_something(int some_param) { 
    // ... 
    return *this; 
    } 
}; 

class derived_exception : public base_exception { 
    // ... 
}; 

int main() { 
    try { 
    // ... 
    if (disaster) 
     throw derived_exception(required_arg1, required_arg2) 
      .set_something(optional_param); 
    } 
    catch (derived_exception const &e) { 
    // terminate called after throwing an instance of 'base_exception' 
    } 
} 

El problema es que set_something() vuelve base_exception pero el catch espera un derived_exception. Por supuesto, un humano puede decir que el tipo real de la excepción es un derived_exception pero aparentemente el compilador no sabe.

Ese es el problema que realmente estoy tratando de resolver, es decir, cómo tener una clase de excepción base para poder establecer parámetros opcionales en el objeto de excepción pero devolver una instancia del tipo derivado. El ejemplo point_2d que di más arriba es (creo) una versión más pequeña y simple del mismo problema para que las personas entiendan y que una solución al problema más pequeño también resolverá mi problema real.

en cuenta que yo tenía intención de hacerla base_exception una plantilla y pase en el tipo derivado como:

template<class Derived> 
class base_exception { 
    // ... 
    Derived& set_something(int some_param) { 
    // ... 
    return *this; 
    } 
}; 

Creo que en realidad no resuelve el problema, pero no es una solución perfecta porque si otra clase more_derived_exception deriva desde derived_exception, entonces volvemos al mismo problema.

+1

El 'base_exception ' idea que tenga al final de su pregunta que se conoce como el patrón de plantilla Curiosamente Recurrente (CRTP). No creo que proporcione una solución en su caso, ya que no podrá tener una sola clase base raíz para su jerarquía de excepciones. –

+0

@Emile: Sí, dije que no es una solución perfecta y por qué. –

+0

Simplemente estaba señalando, para beneficio de otros lectores, que el patrón que ha mostrado tiene un nombre particular. También le estaba dando otra razón por la cual (diferente a la suya) la solución no funcionaría. No era mi intención criticarte. :-) –

Respuesta

0

¿Por qué no vas por el enfoque más simple (quizás no es el más elegante):

if (disaster) 
{ 
    derived_exception e = derived_exception(required_arg1, required_arg2); 
    e.set_something(optional_param); 
    throw e; 
} 

No que resolver su problema o puedo perder algo?

+0

él quiere poder llamar a e.set_something (optional_param) .set_another_something(). el método de clase set_another_something drived no podrá invocarse si el set_something devuelve un puntero a la clase base. – cppanda

+0

@cppanda OK, parece que he entendido mal la pregunta. – sstn

7

Lo que estás buscando es el Named Parameter Idiom, que estoy copiando a this StackOverflow answer. En lugar de devolver una referencia al objeto real, devuelve una referencia a un objeto de parámetro especial y confía en un constructor para que su objeto de excepción realice una conversión implícita una vez que se hayan completado todos los parámetros. En realidad, es bastante inteligente.

+0

+1, parece ser la mejor manera de hacerlo. :) –

+0

+1 para vincular a mi respuesta –

+0

El ejemplo vinculado funciona solo porque la asignación del objeto parámetro al objeto real causa una conversión a través del constructor. En mi caso, no hay oportunidad para una conversión de este tipo mediante el "tiro" que arrojará lo que le des sin conversión. –

1

Hola acabo de tener un problema similar y aquí mi solución:

template<class DerivedOptions> 
class SomeOptions 
{ 
    private: 
    DerivedOptions* derived; 
    int param1_; 
    public: 
    SomeOptions() 
    { 
     derived = reinterpret_cast<DerivedOptions*>(this); 
    } 

    DerivedOptions & set_some_options(int param1) 
    { 
     param1_ = param1; 
     return *derived; 
    } 
}; 

struct MoreOptions: public SomeOptions<MoreOptions> 
{ 
    private: 
    int more_; 
    public: 
    MoreOptions & set_more_options(int more) 
    { 
     more_ = more; 
     return *this; 
    } 
}; 

defininately contiene algunas Sé lo que estoy haciendo foo pero por el otro lado (al menos en mi solicitud) Base la clase no debe usarse sin herencia.

Saludos, Regi

+0

(1) Debería usar static_cast, no reinterpret_cast. (2) No creo que necesite almacenar ningún puntero en absoluto: simplemente puede convertir * esto en el retorno de set_some_options(). (3) Esta solución funciona solo para un nivel de herencia. –

Cuestiones relacionadas