Как вернуть разные классы из одной функции?

У меня есть вопрос, хотя он не ограничивается только С++. Как вернуть совершенно другой класс из одной функции?

f() {

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


}

Например, у меня есть два шара в пространстве, в соответствии с положением и размером, есть три ситуации, когда два шара пересекаются друг с другом, т.е. не пересекаются, в точке, а и круге. Как я могу вернуть другой класс в одну функцию?

Спасибо.

Ответ 1

Если вы можете позволить себе 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);

EDIT: Класс посетителя должен состоять из boost::static_visitor

UPDATE: Подскажите некоторые критические комментарии. Я написал небольшую тестовую программу. Сравниваются четыре подхода:

  • boost::variant
  • union
  • иерархия классов
  • boost::any

Это результаты на моем домашнем компьютере при компиляции в режиме деблокирования с оптимизацией по умолчанию (VC08):

тест с boost:: variant занял 0.011 микросекунды

тест с соединением принимал 0,012 микросекунды

тест с иерархией занял 0.227 микросекунды

тест с boost:: any взял 0.188 микросекунды

Использование boost::variant быстрее, чем объединение и приводит (IMO) к самому изящному коду. Я предполагаю, что чрезвычайно низкая производительность подхода иерархии классов связана с необходимостью использования динамических распределений памяти и динамической отправки. boost::any не является ни быстрым, ни особенно элегантным, поэтому я не буду рассматривать его для этой задачи (у него есть и другие приложения)

Ответ 2

Классы, которые вы хотите вернуть, должны быть получены из общего базового класса. Таким образом, вы можете вернуть базовый тип. Для примера (это не код, просто отмечающий шаблон, вы можете использовать интерфейс, если ваш язык поддерживает эту абстракцию или абстрактный класс, например. Если вы используете С++, вам нужно будет вернуть указатель на общий класс):

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;


}

Ответ 3

В дополнение к предложению @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 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();
  }
}

Ответ 4

Чтобы иметь возможность делать что-либо полезное с результатом, вы должны вернуть объект, который имеет общий базовый класс. В вашем случае вы можете позволить наследовать A, B и C из общего "класса пересечений"; класс, который является общим для всех объектов, которые представляют собой некоторую форму пересечения. Тогда ваша функция f вернет объект этого типа.

Ответ 5

Классы, которые вы хотите вернуть, должны иметь общий родительский класс или интерфейс.
Если эти классы не имеют ничего общего, это, я полагаю, неверно, вы можете вернуться object.
Эта функция также известна как полиморфизм.

Ответ 6

В С++ указатель базового класса может указывать на производный объект класса. Мы можем использовать этот факт для кодирования функции, которая соответствует вашим требованиям:

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

    }
}

Ответ 7

Вы не можете. Вы можете возвращать базовый указатель только для разных производных классов. Если это абсолютно, 100% необходимо, вы можете использовать исключения как уродливые взломы, но это явно не рекомендуется вообще.

Ответ 8

Даже если вы можете вернуть три функции из этой функции, что бы вы сделали с результатом? Вам нужно сделать что-то вроде:

XXX ret_val = getIntersection();

Если getIntersection возвращено три разных типа объектов, XXX должен измениться в зависимости от того, что будет возвращено getIntersection. Ясно, что это совершенно невозможно.

Чтобы справиться с этим, вы можете определить один тип, который определяет достаточно, чтобы охватить все возможности:

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

Теперь getIntersection() может возвращать пересечение, которое определяет, какое у вас есть пересечение (и BTW, вам нужно рассмотреть четвертую возможность: с двумя сферами одинакового радиуса и одной и той же центральной точкой, пересечение будет сферой) и размер и местоположение этого пересечения.

Ответ 9

Имеется еще одна опция. Вы можете вернуть 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, как было предложено Мануэлем, если у вас есть опция.

Ответ 10

И кстати, как вы сказали, вопрос "не ограничивается С++":

1) динамические языки, конечно же, делают кусок пирога:

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

2) некоторые функциональные языки (Haskell, OCaml, Scala, F #) предоставляют приятные встроенные варианты, которые называются Алгебраические типы данных (статья имеет хорошие образцы).

Ответ 11

В языках, которые отражают, их легче достичь. В cpp, если у вас есть стандартный набор возвращаемых классов (указатели), создайте перечисление и верните значение перечисления. Используя это значение, вы можете сделать вывод о типе класса. Это общий способ в случае отсутствия общего родительского класса

Ответ 12

Ограничение основано на объявленном обратном типе вашего метода. В вашем коде указано:

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, как правило, через простое наследование, хотя я не буду разбираться в различиях между подклассами и подтипированием.

Есть подходы, которые могут обойти это. Вы можете создать класс или структуру (С++) с полями для каждого типа возможного возврата и использовать некоторое поле флага, чтобы указать, какое поле является фактическим возвращенным значением.

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

Пример перечисления в другом ответе более похож. Причина, по которой это взломать, заключается в том, что код, обрабатывающий возврат из этого метода, должен иметь много кода для обработки каждой из разных возможностей, например,

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

}

И это, даже не пытаясь сделать это с Исключениями, которые вносят еще большие проблемы. Может быть, я добавлю это позже в качестве редактирования.

Ответ 13

Вы действительно не должны этого делать, и должны действительно придумать лучший дизайн вместо того, чтобы заставлять квадратную привязку в круглом отверстии. И с большинством языков вы не можете сделать это вообще, по дизайну. Вы никогда не будете знать, с чем работаете, и компилятор не успеет заблаговременно, обеспечивая дополнительные ошибки и странное поведение и непостижимость.