2010-02-10 26 views
10

Tengo una pregunta, aunque no se limita a C++. ¿Cómo devolver una clase totalmente diferente de una función?¿Cómo devolver diferentes clases desde una función?

f() { 

in case one: return A; 
in case two: return B; 
in case three: return C; 


} 

Por ejemplo, tienen dos bolas en el espacio, de acuerdo con la posición y el tamaño, existen tres situaciones para las dos bolas para cruzarse entre sí, es decir, no intersección, en el punto, A y círculo. ¿Cómo puedo devolver diferentes clases en una función?

Gracias.

+4

El ejemplo no parece tres tipos diferentes, es una buena solución. Suena más como tres instancias diferentes de una región. ¿Puedes dar un mejor ejemplo o una descripción real de lo que intentas hacer? –

Respuesta

28

Si se lo puede permitir Boost entonces esto suena como una aplicación perfecta para Boost.Variant.

struct NoIntersection { 
    // empty 
}; 
struct Point { 
    // whatever 
}; 
struct Circle { 
    // whatever 
}; 

typedef boost::variant<NoIntersection, Point, Circle> IntersectionResult; 

IntersectionResult intersection_test() { 

    if(some_condition){ 
     return NoIntersection(); 
    } 
    if(other_condition){ 
     return Point(x, y); 
    } 
    if(another_condition){ 
     return Circle(c, r); 
    } 
    throw std::runtime_error("unexpected"); 
} 

A continuación, procesar su resultado con un visitante estática:

struct process_result_visitor : public boost::static_visitor<> { 

    void operator()(NoIntersection) { 
     std::cout << "there was no intersection\n"; 
    } 
    void operator()(Point const &pnt) { 
     std::cout << "there was a point intersection\n"; 
    } 
    void operator()(Circle const &circle) { 
     std::cout << "there was a circle intersection\n"; 
    } 
}; 

IntersectionResult result = intersection_test(); 
boost::apply_visitor(process_result_visitor(), result); 

EDIT: La clase visitante debe proceder de la boost::static_visitor

ACTUALIZACIÓN: Impulsada por algunos comentarios críticos I' ve escrito a little benchmark program.Cuatro enfoques se comparan:

  • boost::variant
  • unión
  • jerarquía de clases
  • boost::any

Estos son los resultados en mi ordenador de casa, cuando compilo en modo de lanzamiento con las optimizaciones por defecto (VC08):

prueba con impulso :: variante tomó 0,011 microsegundos

prueba con la unión tomaron 0,012 microsegundos

prueba con jerarquía tomó 0,227 microsegundos

prueba

con cualquier impulso :: tomaron 0,188 microsegundos

Uso boost::variant es más rápido que una unión y conduce (IMO) al código más elegante. Supongo que el rendimiento extremadamente pobre del enfoque de jerarquía de clases se debe a la necesidad de utilizar asignaciones dinámicas de memoria y despacho dinámico. boost::any no es rápido ni especialmente elegante, por lo que no lo consideraría para esta tarea (aunque tiene otras aplicaciones)

+1

Enfoque interesante, y mejor que 'struct R {enum type {t1, t2, t3}; unión {A a; B b; C c; } valor; } '... – xtofl

+2

+1. La estructura visitante tiene la ventaja adicional de no poder compilar si no maneja uno de los casos. –

+0

@xtofl, habiendo revisado las fuentes, no diría "mejor". 'boost :: variant' sigue la misma idea, pero solo agrega azúcar sintáctico. –

13

Las clases que desea devolver deben derivarse de una clase base común. Entonces, puedes devolver el tipo de base. Por ejemplo (esto no es un código, solo marca el patrón, puede usar una interfaz si su lenguaje admite esta abstracción o clase abstracta, por ejemplo. Si usa C++ tendrá que devolver un puntero de la clase común):

class A : public Common 
{ 
.. 
} 

class B : public Common 
{ 
.. 
} 

class C : public Common 
{ 
.. 
} 

Common f() { 

in case one: return A; 
in case two: return B; 
in case three: return C; 


} 
+1

¿Qué hay de las tres clases no tienen ninguna relación? – skydoor

+9

Tenga en cuenta que en C++ tendrá que devolver un * puntero * al tipo base. –

+13

@skydoor Eso sería un mal diseño, por decir lo menos. –

2

Para poder hacer algo útil con el resultado, debe devolver un objeto que tenga una clase de base común. En su caso, es posible que desee permitir que A, B y C hereden de una "clase de intersección" común; una clase que es común para todos los objetos que representa alguna forma de intersección. Su función f devolvería un objeto de este tipo.

2

Las clases que desea devolver deben tener una clase o interfaz común.
Si esas clases no tienen nada en común, eso, supongo, no es cierto, puede devolver object.
Esta función también se conoce como polimorfismo.

1

No puede. Solo puede devolver un puntero base a diferentes clases derivadas. Si esto es absolutamente, 100% necesario, puedes usar excepciones como un truco feo, pero eso obviamente no es recomendable en absoluto.

+0

Nunca digas que no. Puedes hacerlo de varias maneras, aunque no había considerado excepciones. –

+0

Otras personas han expresado mejores soluciones en este hilo que usando excepciones, de cualquier forma :) – gparent

2

En C++, el puntero de clase base puede apuntar al objeto de clase derivado. Podemos hacer uso de este hecho para codificar una función que satisfaga sus requisitos:

class shape{}; 

class circle: public shape 
{}; 

class square: public shape 
{}; 

shape* function(int i){ // function returning a base class pointer. 

    switch(i) { 

     case 1: return new circle(); 

     case 2: return new square(); 

    } 
} 
0

En los idiomas que reflejan, es más fácil de lograr. En cpp, si tiene un conjunto estándar de clases para devolver (punteros), cree una enumeración y devuelva el valor enum. Usando este valor, puede inferir el tipo de clase. Esta es una forma genérica en caso de que no exista una clase padre común

1

Incluso si pudiera devolver tres tipos diferentes de objetos de la función, ¿qué haría con el resultado? Es necesario hacer algo como:

XXX ret_val = getIntersection(); 

Si getIntersection regresaron tres tipos diferentes de objetos, XXX tendría que cambiar en función de lo que era getIntersection va a volver. Claramente esto es completamente imposible.

Para hacer frente a esto, se puede definir un tipo que define lo suficiente para cubrir todas las posibilidades:

class Intersection { 
    enum { empty, point, circle, sphere}; 
    point3D location; 
    size_t radius; 
}; 

Ahora getIntersection() puede devolver una intersección que define qué tipo de intersección que tiene (y por cierto, es necesario para considerar la cuarta posibilidad: con dos esferas del mismo radio y el mismo punto central, la intersección será una esfera) y el tamaño y la ubicación de esa intersección.

1

Hay otra opción disponible. Puede devolver un union de punteros a objetos junto con una etiqueta que le dice a la persona que llama qué miembro de la unión es válido. Algo así como:

struct result { 
    enum discriminant { A_member, B_member, C_member, Undefined } tag; 
    union result_data { 
     A *a_object; 
     B *b_object; 
     C *c_object; 
    } data; 
    result(): tag(Undefined) {} 
    explicit result(A *obj): tag(A_member) { data.a_object = obj; } 
    explicit result(B *obj): tag(B_member) { data.b_object = obj; } 
    explicit result(C *obj): tag(C_member) { data.c_object = obj; } 
}; 

utilizaría probablemente Boost.variant as suggested by Manuel si usted tiene la opción.

6

Además de la sugerencia @ Manuel Boost.Variant, eche un vistazo a Boost.Any: tiene un propósito similar al de Boost.Variant, pero con diferentes compensaciones y funcionalidad.

boost :: any no tiene límites (puede contener cualquier tipo) mientras boost :: variant está limitado (los tipos compatibles están codificados en tipo de variante, por lo que solo pueden contener valores de estos tipos).

// from Beyond the C++ Standard Library: An Introduction to Boost 
// By Björn Karlsson 

#include <iostream> 
#include <string> 
#include <utility> 
#include <vector> 
#include "boost/any.hpp" 

class A { 
public: 
    void some_function() { std::cout << "A::some_function()\n"; } 
}; 

class B { 
public: 
    void some_function() { std::cout << "B::some_function()\n"; } 
}; 

class C { 
public: 
    void some_function() { std::cout << "C::some_function()\n"; } 
}; 

int main() { 
    std::cout << "Example of using any.\n\n"; 

    std::vector<boost::any> store_anything; 

    store_anything.push_back(A()); 
    store_anything.push_back(B()); 
    store_anything.push_back(C()); 

    // While we're at it, let's add a few other things as well 
    store_anything.push_back(std::string("This is fantastic! ")); 
    store_anything.push_back(3); 
    store_anything.push_back(std::make_pair(true, 7.92)); 

    void print_any(boost::any& a); 
    // Defined later; reports on the value in a 

    std::for_each(
    store_anything.begin(), 
    store_anything.end(), 
    print_any); 
} 

void print_any(boost::any& a) { 
    if (A* pA=boost::any_cast<A>(&a)) { 
    pA->some_function(); 
    } 
    else if (B* pB=boost::any_cast<B>(&a)) { 
    pB->some_function(); 
    } 
    else if (C* pC=boost::any_cast<C>(&a)) { 
    pC->some_function(); 
    } 
} 
+1

Me gustaría ver esa función 'print_any'. – Novelocrat

+0

El código se actualiza. –

+1

Bastante extenso, y ciertamente menos auto-documentado que una 'variante' ya que cualquier cosa puede ser devuelta ... también, está la sobrecarga de rendimiento a considerar,' any' requiere verificación de tiempo de ejecución del tipo usando RTTI mientras 'variant' almacena el tipo en un miembro de datos. –

0

La limitación se basa en el tipo de devolución declarada de su método. El código establece:

f() { 
    in case one: return A; 
    in case two: return B; 
    in case three: return C; 
} 

Cuando en realidad el compilador requiere algo como esto:

FooType f() { 
    in case one: return A; 
    in case two: return B; 
    in case three: return C; 
} 

Debe ser posible convertir el A, B, y C a un FooType, normalmente a través de la herencia simple, aunque no entraré en las diferencias entre las subclases frente a la subtipificación.

Existen enfoques que pueden evitar esto. Puede crear una clase o estructura (C++) que tenga campos para cada tipo diferente de retorno posible y usar algún campo de indicador para indicar qué campo es el valor real devuelto.

class ReturnHolder { 
    public int fieldFlag; 
    public TypeA A; 
    public TypeB B; 
    public TypeC C; 
} 

El ejemplo enum en otra respuesta es más de lo mismo. La razón por la que es un truco es que el código que gestiona el regreso de este método tendrá que tener un montón de código para controlar cada una de las diferentes posibilidades, al igual que

main(){ 

FooType *x = new FooType(); 
ReturnHolder ret = x.f(); 

switch (ret.fieldFlag) 
    case: 1 
     //read ret.A 

    case: 2 
     //read ret.B 

    case: 3 
     //read ret.C 

} 

Y eso es sin tener que ir a tratar de hazlo con Excepciones que introducen problemas aún mayores. Tal vez lo agregue más adelante como una edición.

1

Y, por cierto, como usted ha dicho que la cuestión "no se limita a C++":

1) lenguajes dinámicos, por supuesto, hacen que sea pan comido:

# python 
def func(i): 
    if i == 0: 
    return 0 
    elif i == 1: 
    return "zero" 
    else 
    return() 

2) algunos los lenguajes funcionales (Haskell, OCaml, Scala, F #) proporcionan agradables variantes incorporadas que se llaman Algebraic Data Types (el artículo tiene buenas muestras).

0

Realmente no debería querer estar haciendo eso, y realmente debería tener un mejor diseño en lugar de forzar una clavija cuadrada en un agujero redondo. Y con la mayoría de los idiomas no puedes hacerlo en absoluto, por diseño. Nunca sabrá realmente con qué está trabajando, y el compilador tampoco lo hará con anticipación, lo que garantiza errores adicionales y un comportamiento extraño e incomprensible.

Cuestiones relacionadas