开发者

How to return different classes from one function?

I have a question, though it is not limited to C++. How to return totally different class from one function?

f() {

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


}

For example, I have two balls in the space, according to the position and the size, there are three situations for the two balls to intersect with each other, i.e, non-intersection, at point, a and circle. How 开发者_如何学Pythoncan I return different class in one function?

Thanks.


If you can afford Boost then this sounds like a perfect application for 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");
}

You then process your result with a static visitor:

 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: The visitor class must derive from boost::static_visitor

UPDATE: Prompted by some critical comments I've written a little benchmark program. Four approaches are compared:

  • boost::variant
  • union
  • class hierarchy
  • boost::any

These are the results in my home computer, when I compile in release mode with default optimizations (VC08):

test with boost::variant took 0.011 microseconds

test with union took 0.012 microseconds

test with hierarchy took 0.227 microseconds

test with boost::any took 0.188 microseconds

Using boost::variant is faster than a union and leads (IMO) to the most elegant code. I'd guess that the extremely poor performance of the class hierarchy approach is due to the need to use dynamic memory allocations and dynamic dispatch. boost::any is neither fast nor especially elegant so I wouldn't consider it for this task (it has other applications though)


The classes you want to return should be derived from a common base class. So, you can return the base type. For Example (this is not a code, just marking the pattern, you can use an interface if your language supports this abstraction or abstract class for example. If you use C++ you will have to return a pointer of the common class):

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;


}


In addition to @Manuel's Boost.Variant suggestion, take a look at Boost.Any: has similar purpose as Boost.Variant but different tradeoffs and functionality.

boost::any is unbounded (can hold any type) while boost::variant is bounded (supported types is encoded in variant type, so it can hold only values of these types).

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


In order to be able to do anything useful with the result, you have to return an object which has a common baseclass. In your case you might want to let A, B, and C inherit from a common "intersection-class"; a class which is common for all objects which represents some form of intersection. Your function f would then return an object of this type.


The classes you want to return should have a common parent class or interface.
If those classes do not have anything in common, that, I suppose, is untrue, you can return object.
This feature is also known as polymorphism.


In c++ base class pointer can point to derived class object. We can make use of this fact to code a function that meets your requirements:

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

    }
}


There is one other option available. You can return a union of pointers to objects along with a tag that tells the caller which member of the union is valid. Something like:

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

I would probably use Boost.variant as suggested by Manuel if you have the option.


You can't. You can only return a base pointer to different derived classes. If this is absolutely, 100% needed, you can use exceptions as a ugly hack, but that's obviously not recommended at all.


Even if you could return three different types of objects from the function, what would you do with the result? You need to do something like:

XXX ret_val = getIntersection();

If getIntersection returned three different types of objects, XXX would have to change based on what getIntersection was going to return. Clearly this is quite impossible.

To deal with this, you can define one type that defines enough to cover all the possibilities:

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

Now getIntersection() can return an Intersection that defines what kind of intersection you have (and BTW, you need to consider the fourth possibility: with two spheres of the same radius and same center point, the intersection will be a sphere) and the size and location of that intersection.


The limitation is based on the declared return type of your method. Your code states:

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

When in reality the compiler requires something like this:

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

It must be possible to convert the A, B, and C to a FooType, typically through simple inheritance, though I won't get into the differences between subclasses vs subtyping.

There are approaches that can get around this. You could create a class or struct (C++) which has fields for each different type of possible return and use some flag field to indicate which field is the actual returned value.

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

The enum example in another answer is more of the same. The reason why that is a hack is that the code that handles the return from this method will have to have lots of code to handle each of the different possibilites, like so

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

}

And that's without even going into trying to do it with Exceptions which introduce even bigger problems. Maybe I'll add that in later as an edit.


And by the way, as you said that question "is not limited to C++":

1) dynamic languages, of course, make it piece of cake:

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

2) some functional languages (Haskell, OCaml, Scala, F#) provide nice built-in variants that are called Algebraic Data Types (article has good samples).


In languages that reflection, it is easier to achieve. In cpp, if you have a standard set of classes to be returned (pointers), create an enumeration and return the enum value. Using this value you can infer the class type. This is a generic way in case there is no common parent class


You really shouldn't want to be doing that, and should really come up with a better design instead of forcing a square peg in a round hole. And with most languages you can't do it at all, by design. You will never really know what you are working with, and neither will the compiler ahead of time, ensuring extra bugs and weird behavior and incomprehensibility.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜