【问题标题】:Dynamically selecting the comparison functor to be used in std::map动态选择要在 std::map 中使用的比较函子
【发布时间】:2019-10-18 05:00:17
【问题描述】:

我有一组数据需要按照用户在运行时决定的标准进行排序。

理想情况下,这个排序标准必须作为参数传递给函数,例如:

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

所有这些都可以正常工作。但是,我认为实现我想要的东西太复杂了,而且我感觉必须使用多态特性意味着“真正的”函子不能再被内联,导致可能(虽然我还没有测量) 性能损失。

我不确定这是否可以使用模板以更简单的方式解决(因为函子选择是在运行时完成的)。

有什么建议吗?还是我只是想多了这个问题,这是一个可以接受的解决方案?性能怎么样?

我不在mainFunction() 接口中使用纯C 风格的函数(bool (*)(InputDataType const &amp;var1, InputDataType const &amp;var2)) 来访问函子提供的额外功能,例如状态、构造参数等。

非常感谢您的建议。

【问题讨论】:

  • std::map&lt;InputDataType, ValueType, std::function&lt;bool(const IntputDataType&amp;, const InputDataType&amp;)&gt;&gt;?
  • @MilesBudnek 你应该这样回答。 OP 可能知道也可能不知道如何正确使用std::function。其他读者也是如此。
  • 对此要非常小心。如果您在地图中有元素后更改排序方法,那么您就有了 UB。
  • 您可以为所有可能的排序器使用std::variantstd::map,并通过访问者访问它,然后代码将被内联。但它会更复杂,更不灵活,所以你最好在需要时进行衡量。

标签: c++ polymorphism functor stdmap


【解决方案1】:

您可以使用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 负责类型擦除和存储(副本)您用于构建地图比较器的函数对象。

另请注意,我已将比较器标记为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

这显然不太灵活,但它可以提供更好的性能。同样,您必须进行基准测试才能确定增益是否可测量,更不易察觉。

【讨论】:

    猜你喜欢
    • 2015-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-14
    • 2012-05-27
    • 2017-07-28
    • 2013-01-05
    • 2023-03-05
    相关资源
    最近更新 更多