Подтвердить что ты не робот

Полиморфизм возвращаемого типа для передачи по значению

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

Первоначально у меня было что-то вроде:

class Operand;

Operand genOperandA() { ...; return Operand(); }
Operand genOperandB() { ...; return Operand(); }
... // more operand-generation functions

typedef Operand (*OpGen)();

// Table of function pointers
static const OpGen generators[] =
{
    genOperandA,
    genOperandB,
    ...
};

// Function to do some operation on the operand
void operate(Operand& op);

...

// Example call
operate(generators[1]());

Пока так хорошо (я думаю). Однако теперь существует несколько типов операндов, например. class RegisterOperand : public Operand. У меня есть новые выделенные функции genOperand, которые идеально возвращают экземпляры производных типов. Но я не могу этого сделать:

Operand genOperandC() { ...; return RegisterOperand(); }

и я не могу этого сделать:

RegisterOperand genOperandC() { ...; return RegisterOperand(); }

static const OpGen generators[] = 
{
    ...
    genOperandC,
};

Однако я знаю, что это сработает, если я вернусь к ссылкам или типам указателей, поэтому единственный вариант, который у меня есть сейчас, это что-то вроде:

Operand *genOperandC() { ...; return new RegisterOperand(); }

который теперь требует явной очистки, которая изначально не требовалась.

Любые альтернативы, которые я не рассматривал?

4b9b3361

Ответ 1

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


Если возвращение указателя является проблемой (из-за необходимости "очистки" вещей), вам определенно следует использовать интеллектуальные указатели в качестве типа возврата.

Вот пример вашего метода factory со смарт-указателями:

boost::shared_ptr<Operand> genOperandC()
{
  return boost::shared_ptr<Operand>(new RegisterOperand());
}

Таким образом, вам не нужно будет вызывать delete вручную: это будет выполняться деструктором boost::shared_ptr<Operand> для вас, когда потребуется.

Если впоследствии вам нужно отбросить полученный указатель, boost также предоставляет функции кастинга:

boost::shared_ptr<Operand> op = genOperandC();

boost::shared_ptr<RegisterOperand> rop =
  boost::dynamic_pointer_cast<RegisterOperand>(op);

Ответ 2

Вы можете обернуть:

class Operand
{
public:

private:
  std::unique_ptr<OperandImpl> mImpl;
};

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

Ответ 3

Как насчет чего-то подобного?

Обратите внимание, что вы не можете просто работать (генераторы [i]()), так как исходная операция() принимает неконстантную ссылку.

#include <iostream>
#include <string>
#include <memory>

class Operand {
public:
        Operand(std::string x = "Operand"): x(x) {}
        const std::string x;
};

class RegisterOperand: public Operand {
public:
        RegisterOperand(std::string x = "RegisterOperand")
                : Operand(x) {}
};

typedef std::auto_ptr<Operand> POperand;

POperand genOperandA() { return POperand(new Operand("genOperandA")); }
POperand genOperandB() { return POperand(new Operand("genOperandB")); }
POperand genOperandC() { return POperand(new RegisterOperand()); }
// more operand-generation functions

typedef POperand (*OpGen)();

// Table of function pointers
static const OpGen generators[] =
{
        genOperandA,
        genOperandB,
        genOperandC,
};

void operate(const POperand& op)
{
        std::cout << op->x << std::endl;
}

int main()
{
        operate(generators[0]());
        operate(generators[1]());
        operate(generators[2]());
        return 0;
}

Ответ 4

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

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

class PolymorphicType {
public: 
     /* 
         Class interface ...
     */
     PolymorphicType() { it = nullptr; }
     virtual ~PolymorphicType() { if(it != nullptr) delete it; }         

     PolymorphicType& operator=(const PolymorphicType& org) {
          //Clone the derived type and store it
          if(org.it != nullptr)
              it = org.it->clone();
     }
private:
     Base* it;
};

Каждый производный класс должен теперь реализовать свой собственный метод клонирования, и вам хорошо идти! И на всякий случай это хорошая статья, объясняющая, как работает клонирование производных типов: Копирование полученных сущностей с использованием только указателей базового класса (без исчерпывающего тестирования!) - С++

Надеюсь, это поможет кому-то!