如何从一个函数返回不同的类?

时间:2010-02-10 15:08:02

标签: c++

我有一个问题,但它不仅限于C ++。如何从一个函数返回完全不同的类?

f() {

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


}

例如,我在空间中有两个球,根据位置和大小,两个球相互交叉有三种情况,即非交点,点,a和圆。如何在一个函数中返回不同的类?

感谢。

13 个答案:

答案 0 :(得分:30)

如果你能买得起Boost,那么这听起来像是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");
}

然后,您使用静态访问者处理结果:

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

编辑:访问者类必须来自boost::static_visitor

更新:我写过a little benchmark program的一些批评性评论提示。比较了四种方法:

  • boost::variant
  • union
  • 类层次结构
  • boost::any

当我使用默认优化(VC08)在发布模式下编译时,这些是我家用计算机中的结果:

  

使用boost :: variant进行测试需要0.011微秒

     

使用union测试需要0.012微秒

     

层次结构测试花费0.227微秒

     

使用boost :: any进行测试需要0.188微秒

使用boost::variant比联盟更快,并引导(IMO)到最优雅的代码。我猜测类层次结构方法的极差性能是由于需要使用动态内存分配和动态调度。 boost::any既不快也不优雅,所以我不会考虑这个任务(它有其他应用程序)

答案 1 :(得分:13)

要返回的类应该从公共基类派生。因此,您可以返回基本类型。例如(这不是代码,只是标记模式,如果您的语言支持此抽象或抽象类,则可以使用接口。如果使用C ++,则必须返回公共类的指针):

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;


}

答案 2 :(得分:6)

除了@ Manuel的Boost.Variant建议外,请查看Boost.Any:与Boost.Variant具有相似的目的,但有不同的权衡和功能。

boost :: any是无界的(可以包含任何类型),而boost :: variant是有界的(受支持的类型以变体类型编码,因此它只能包含这些类型的值)。

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

答案 3 :(得分:2)

为了能够对结果执行任何有用的操作,您必须返回具有公共基类的对象。在你的情况下,你可能想让A,B和C继承一个共同的“交叉类”;一个对所有对象都通用的类,它代表某种形式的交集。然后你的函数f将返回这种类型的对象。

答案 4 :(得分:2)

您要返回的类应具有公共父类或接口 如果这些课程没有任何共同之处,我认为这是不真实的,你可以返回object
此功能也称为多态

答案 5 :(得分:2)

在c ++基类中,指针可以指向派生类对象。我们可以利用这个事实来编写满足您要求的函数:

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

    }
}

答案 6 :(得分:1)

你做不到。您只能返回指向不同派生类的基指针。如果这是绝对的,100%需要,你可以使用异常作为一个丑陋的黑客,但显然不建议这样做。

答案 7 :(得分:1)

即使你可以从函数中返回三种不同类型的对象,你会对结果做些什么?您需要执行以下操作:

XXX ret_val = getIntersection();

如果getIntersection返回了三种不同类型的对象,则XXX必须根据要返回的getIntersection 进行更改。显然这是不可能的。

为了解决这个问题,您可以定义一种足以涵盖所有可能性的类型:

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

现在getIntersection()可以返回一个交点,它定义了你有什么样的交叉点(而BTW,你需要考虑第四种可能性:两个相同半径和相同中心点的球体,交点将是一个球体)以及该交叉点的大小和位置。

答案 8 :(得分:1)

还有另一种选择。您可以返回一个指向对象的union指针以及一个告诉调用者哪个成员有效的标记。类似的东西:

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

如果你有选择,我可能会使用Boost.variant as suggested by Manuel

答案 9 :(得分:1)

顺便说一句,正如你所说的那样“不仅限于C ++”:

1)动态语言当然会让它变得轻而易举:

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

2)一些函数式语言(Haskell,OCaml,Scala,F#)提供了很好的内置变体,称为Algebraic Data Types(文章有很好的样本)。

答案 10 :(得分:0)

在反思的语言中,它更容易实现。在cpp中,如果要返回一组标准类(指针),则创建一个枚举并返回枚举值。使用此值可以推断出类类型。如果没有共同的父类

,这是一种通用的方法

答案 11 :(得分:0)

该限制基于您声明的方法返回类型。您的代码声明:

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

实际上编译器需要这样的东西:

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

必须可以将A,B和C转换为FooType,通常是通过简单的继承,但我不会介入子类与子类型之间的差异。

有一些方法可以解决这个问题。您可以创建一个类或结构(C ++),其中包含每种不同类型的可能返回的字段,并使用一些标志字段来指示哪个字段是实际返回的值。

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

另一个答案中的枚举示例更多相同。这就是hack的原因是处理来自此方法的返回的代码必须有很多代码来处理每个不同的可能性,如此

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

}

而且甚至没有试图用引入更大问题的异常来做这件事。也许我稍后会在编辑中添加它。

答案 12 :(得分:0)

你真的不应该这样做,而且应该真正想出一个更好的设计,而不是在一个圆孔中强迫一个方形钉。对于大多数语言而言,根本不可能通过设计来完成。你永远不会真正知道你在做什么,而且编译器也不会提前,确保额外的错误和奇怪的行为和不可理解性。