Динамический выбор функтора сравнения для использования в std :: map - PullRequest
0 голосов
/ 17 октября 2019

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

В идеале этот критерий сортировки должен передаваться как параметр в функции, такой как:

void mainFunction(
    InputData const &inputData,
    OutputData &outputData,
    BaseSortingCriteria const &compareFunctor)
{

    /* The data is sorted by this map using the custom provided functor as criteria */
    std::map<InputDataType, ValueType, BaseSortingCriteria> sortedSets(compareFunctor);

    ...
}

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

struct VirtualSortingCriteria
{
    virtual bool operator()(
        const InputDataType &var1,
        const InputDataType &var2) const = 0;
}

И чтобы сохранить общий интерфейс, базовый функтор, который простовыполняет «настоящий» функтор, переданный во время построения:

struct BaseSortingCriteria
{
    BaseSortingCriteria(
        std::shared_ptr<const VirtualSortingCriteria> pCompareFunctor) :
        m_realCompareFunctor(pCompareFunctor)
    {
    }

    bool operator()(
        const InputDataType &var1,
        const InputDataType &var2)
    {
        return m_realCompareFunctor->operator()(var1, var2);
    }

private:
    /** Pointer to the real functor to be used. */
    std::shared_ptr<const VirtualSortingCriteria> const m_realCompareFunctor;
}

И я определил пару «настоящих» функторов для проверки:

struct RealFunctorVersionA final : public VirtualSortingCriteria
{
    bool operator () (
        InputDataType const &var1,
        InputDataType const &var2) const;
};

struct RealFunctorVersionB final : public VirtualSortingCriteria
{
    bool operator () (
        InputDataType const &var1,
        InputDataType const &var2) const;
};

Код, который на самом деле использует эти разныеКритерии сортировки выглядят так:

std::shared_ptr<VirtualSortingCriteria> cmpFunctor;
switch(userSelectedSortingCriteria)
{
    case CRITERIA_A:
        cmpFunctor.reset(new RealFunctorVersionA);
        break;
    case CRITERIA_B:
        cmpFunctor.reset(new RealFunctorVersionB);
        break;
    default:
        break;
}

BaseSortingCriteria baseCmpFunctor(cmpFunctor);
mainFunction(inputData, outputData, baseCmpFunctor);

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

Я не уверен, что это можно было бы проще исправить с помощью шаблонов (поскольку выбор функтора выполняется во время выполнения).

Есть предложения? Или я просто обдумываю проблему, и это приемлемое решение? Как насчет производительности?

Я не использую простые функции в стиле C (bool (*)(InputDataType const &var1, InputDataType const &var2)) в интерфейсе mainFunction(), чтобы иметь возможность доступа к дополнительным функциям, предоставляемым функторами, таким как состояние, параметры построения и т. Д.

Заранее большое спасибо за совет.

1 Ответ

2 голосов
/ 17 октября 2019

Вместо полиморфизма и прокрутки собственного типа, вы можете использовать std::function. Например:

struct RealFunctorVersionA
{
    bool operator () (const InputDataType& var1, const InputDataType& var2) const;
};

struct RealFunctorVersionB
{
    bool operator () (const InputDataType& var1, const InputDataType& var2) const;
};

using MyMapType = std::map<
    InputDataType,
    ValueType,
    const std::function<bool(const InputDataType&, const InputDataType&)>
>;

MyMapType map_a{RealFunctorVersionA{}}
MyMapType map_b{RealFunctorVersionB{}}

Live Demo

Теперь вам не нужен базовый класс. Вместо этого std::function заботится о стирании типов и хранении (копии) объекта fuctor, который вы используете для построения компаратора вашей карты.

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


Что касается производительности, ваши вызовы функций могут и никогда не будут внутренними, если вы используете метод диспетчеризации во время выполнения. Встраивание вызова функции - это процесс времени компиляции, поэтому его просто невозможно сделать, если у вас есть потенциально неограниченный набор потенциальных вызовов во время компиляции. Всегда будьте осторожны с преждевременной оптимизацией. Никогда не принимайте решения о производительности без бенчмаркинга.

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


Если вы хотите, чтобы компилятор потенциально встроил вызовы в ваш компаратор, вам нужно сделать его статически вызываемым. Вы можете использовать какое-то состояние, чтобы звонки имели другое поведение. Например:

struct MyComparator
{
    bool reverse;
    bool operator () (const InputDataType& var1, const InputDataType& var2) const
    {
        // ...
        if (reverse) {
            return var1 > var2;
        } else {
            return var1 < var2;
        }
    }
};

using MyMapType = std::map<
    InputDataType,
    ValueType,
    const MyComparator
>;

MyMapType map_a{MyComparator{false}};
MyMapType map_a{MyComparator{true}};

Live Demo

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

...