Как вернуть разные классы из одной функции? - PullRequest
11 голосов
/ 10 февраля 2010

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

f() {

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


}

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

Спасибо.

Ответы [ 13 ]

30 голосов
/ 10 февраля 2010

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

ОБНОВЛЕНИЕ: В ответ на некоторые критические комментарии я написал небольшую программу для тестирования . Четыре подхода сравниваются:

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

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

тест с бустом :: вариант занял 0,011 мкс

тест с объединением занял 0,012 мкс

тест с иерархией занял 0,227 мкс

тест с бустом :: любой занял 0,188 мкс

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

13 голосов
/ 10 февраля 2010

Классы, которые вы хотите вернуть, должны быть получены из общего базового класса. Итак, вы можете вернуть базовый тип. Например (это не код, а просто маркировка шаблона, вы можете использовать интерфейс, например, если ваш язык поддерживает эту абстракцию или абстрактный класс. Если вы используете 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;


}
6 голосов
/ 10 февраля 2010

В дополнение к предложению @ Manuel's Boost.Variant обратите внимание на Boost.Any : имеет то же назначение, что и Boost.Variant, но отличается компромиссами и функциональностью.

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

// 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();
  }
}
2 голосов
/ 10 февраля 2010

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

    }
}
2 голосов
/ 10 февраля 2010

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

2 голосов
/ 10 февраля 2010

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

1 голос
/ 10 февраля 2010

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

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

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

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

1 голос
/ 10 февраля 2010

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

1 голос
/ 10 февраля 2010

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

XXX ret_val = getIntersection();

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

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

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

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

1 голос
/ 10 февраля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...