Как обойти запрет на использование виртуальных шаблонов? - PullRequest
2 голосов
/ 22 февраля 2012

Мое первое знакомство с C ++ - это создание библиотеки синтеза звука (EZPlug).

Библиотека разработана для того, чтобы упростить настройку графика взаимосвязанных объектов генератора звука и процессора.Мы можем вызвать EZPlugGenerators

Все процессорные модули могут принимать один или несколько EZPlugGenerators в качестве входных данных.

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

            mixer.addGenerator(
            a(new Panner())
            ->setVolume(0.1)
            ->setSource(
                a(new TriggererPeriodic())
                ->setFrequency(
                    v(new FixedValue(1), "envTriggerFreq")
                )
                ->setTriggerable(
                    a(new Enveloper())
                    ->setAllTimes(v(0.0001), v(0.05), v(0.0f, "envSustain"), v(0.01))
                    ->setAudioSource(
                        a(new SineWaveMod())
                        ->setFrequency(
                            a(new Adder())
                            ->addGenerator(a(new Adder()))
                            ->addGenerator(v(5000, "sineFreq"))
                            ->addGenerator(
                                a(new Multiplier())
                                ->addVal(v("sineFreq"))
                                ->addVal(
                                    a(new TriggererPeriodic())
                                    ->setFrequency(v("envTriggerFreq"))
                                    ->setTriggerable(
                                        a(new Enveloper())
                                        ->setAllTimes(0.1, 0.1, 0, 0.0001)
                                        ->setAudioSource(v(1, "envAmount"))
                                    )
                                )
                            )
                        )
                    )
                )

            )
        );

Функции "a" и "v" в приведенном выше хранилище кода и возвращают ссылкик объектам и обрабатывать их извлечение и уничтожение.

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

Теперь на мой вопрос

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

Это недопустимо:

EZPlugProcessor* addInput(EZPlugGenerator* generator)

, потому что это возвращает указатель на экземпляр суперкласса, а не на подкласс, разрушающий цепочечность, которой я так доволен.

Я пробовал это:

template<typename T> virtual T* addInput(EZPlugGenerator* obj){

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

У меня нет необходимости использовать наследование здесь.Я могу реализовать addInput на каждом EZPlugGenerator, который может принимать входные данные.Похоже, что объединение их всех в один родительский класс поможет понять, что у них всех есть что-то общее, и поможет закрепить тот факт, что addInput является правильным способом подключения одного объекта к другому.

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

Ответы [ 2 ]

4 голосов
/ 22 февраля 2012

Виртуальные функции в C ++ могут иметь ковариантные возвращаемые типы, что означает, что вы можете определить

virtual EZPlugProcessor *addInput(EZPlugGenerator* generator) = 0;

в базовом классе, а затем

struct MyProcessor : EZPlugProcessor {
    virtual MyProcessor *addinput(EZPlugGenerator* generator) { 
        ...
        return this;
    }
};

Пока вызывающая сторона знает (по типу, который они используют), что объект является MyProcessor, они могут связывать addInput вместе с другими функциями, специфичными для MyProcessor.

Если ваша иерархия наследования имеет больше уровней, то, к сожалению, вы иногда будете писать:

struct MySpecificProcessor : MyProcessor {
    virtual MySpecificProcessor *addinput(EZPlugGenerator* generator) {
        return static_cast<MySpecificProcessor*>(MyProcessor::addInput(generator));
    }
};

потому что в EZPlugProcessor нет способа указать, что тип возвращаемого значения addInput - это "указатель на наиболее производный тип объекта". Каждый производный класс должен «активировать» ковариацию для себя.

1 голос
/ 22 февраля 2012

Да, в C ++ уже предусмотрены ковариантные типы возвращаемых данных.

class Base
{
public:
    virtual Base* add() = 0 { return <some base ptr>; }
};

class Child : public Base
{
public:
    virtual Child* add() { return <some child ptr>; }
};

С другой стороны, никто никогда не сможет прочитать ваш код, поэтому вы можете подумать, есть ли альтернативный способ настройки конфигурации, чем написание цепочки LISP в C ++.

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