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

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

f() {

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


}

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

Спасибо.

11
задан skydoor 10 February 2010 в 15:08
поделиться

13 ответов

Если вы можете позволить себе 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
  • class hierarchy
  • boost::any

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

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

тест с union занял 0. 012 микросекунд

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

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

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

.
30
ответ дан 3 December 2019 в 00:48
поделиться

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

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;


}
13
ответ дан 3 December 2019 в 00:48
поделиться

В дополнение к предложению Мануэля 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();
  }
}
6
ответ дан 3 December 2019 в 00:48
поделиться

В базовом классе 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
ответ дан 3 December 2019 в 00:48
поделиться

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

2
ответ дан 3 December 2019 в 00:48
поделиться

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

2
ответ дан 3 December 2019 в 00:48
поделиться

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

1
ответ дан 3 December 2019 в 00:48
поделиться

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

XXX ret_val = getIntersection();

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

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

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

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

1
ответ дан 3 December 2019 в 00:48
поделиться

Доступен еще один вариант. Вы можете вернуть объединение указателей на объекты вместе с тегом, который сообщает вызывающей стороне, какой член объединения является допустимым. Что-то вроде:

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
ответ дан 3 December 2019 в 00:48
поделиться

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

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

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

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

1
ответ дан 3 December 2019 в 00:48
поделиться

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

.
0
ответ дан 3 December 2019 в 00:48
поделиться

Объект Range имеет свойства ширины и высоты, которые измеряются в точках.

-121--1609743-

FWIW У меня была та же проблема (CC.Net отключает Nant, который выполняет компиляцию, и NUnit), и мой вывод NUnit также не появлялся на CC.Net. У меня уже была задача < слияние > в моей задаче < издатель > (и перед задачей < xmllogger > ) и ничего.

Одно, что я сделал не иметь, b/c я явно не нуждался, это был узел < workingDirectory > в моем < project > . Как только я добавил, что мой NUnit вывод появился сразу. Похоже, там есть зависимость по какой-либо причине. Надеюсь, это поможет некоторым из вас.

-121--3384242-

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

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

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

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

}

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

0
ответ дан 3 December 2019 в 00:48
поделиться

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

0
ответ дан 3 December 2019 в 00:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: