Полиморфизм возвращаемого типа для передачи по значению - PullRequest
11 голосов
/ 21 июня 2010

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

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

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

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

Какие альтернативы я не рассматривал?

Ответы [ 4 ]

7 голосов
/ 21 июня 2010

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

class Operand
{
public:

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

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

4 голосов
/ 21 июня 2010

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


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

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

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);
0 голосов
/ 05 мая 2017

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

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

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

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

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

0 голосов
/ 21 июня 2010

Как насчет этого?

Обратите внимание, что вы не можете просто выполнить операцию (генераторы [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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...