【问题标题】:C++11 std::function and std::reference wrapper used for sorting std::set用于排序 std::set 的 C++11 std::function 和 std::reference 包装器
【发布时间】:2023-04-05 11:35:01
【问题描述】:

我有一个 C++ 类,其中一个字段是 std::set 对象。我想自己写一个比较函数,或者让用户指定一个。在 C++11 中有一种处理泛型函数类型的新方法:std::function。它适用于函数指针、成员函数指针、lambda 函数等。

我尝试编写一个简单的实验程序,但它一直在卡顿,即使我按照 C++11 维基百科文章的建议进行操作。也许我只是不明白 std::function 和 std::ref 应该如何使用。

无论如何,关键是当我从一个简单的 lambda 函数创建一个 std::function 并使其成为类成员时,该类的 sizeof 增长了 22。当我从一个指向的指针创建一个 std::function 时一个全局函数,这个 std::function 的 sizeof 是 32。所以大小很大。我将有许多对象使用相同的比较函数,所以我更喜欢让所有对象都使用一个函数。

我有两个想法,告诉我你的想法。一个想法,使用 std::ref 存储对函数的引用,这样我可以定义一个函数,许多对象将使用它来比较 std::set 元素。第二个想法:如果它不能那样工作,或者生成的函数对象太大,也许我可以使用 shared_ptr。

您可能想知道:为什么没有一个静态 std::function 成员?答案是:因为所有对象都将使用相同的比较函数。例如,我希望能够拥有 1000 个对象,其中 400 个使用一个比较函数,600 个使用不同的比较函数。

例子:

class MyClass
{
public:
private:
     std::function<bool (int, int)> compare;
     std::set<int> set;
};

现在如何让 std::set 使用 std::function,并让多个 MyClass 对象使用相同的函数?

我希望能够在运行时更改比较功能,以便用户能够选择集合中对象的顺序(由 GUI 显示)。

【问题讨论】:

  • 与其用文字描述你所做或想要做的事情,不如创建一个SSCCE 并向我们展示实际代码?
  • 因为我从来没有听说过任何叫做 SSCCE 的东西,而且我没有实际的代码;我发布了这个问题,因为我正在寻找如何正确编写它的指导。 (我得到了指导,请参阅下面的答案)

标签: c++ c++11 std functor std-function


【解决方案1】:

表示共享所有权的标准方法是使用std::shared_ptr。这会增加一些开销,迫使您在堆上分配 std::function,但 shared_ptr 小于 std::function 并且它将正确管理其生命周期,因此当任何对象仍在使用函数对象时,它将保持存活,并在不再需要时自动销毁。

正如您所建议的,引用共享函数的reference_wrapper 可以用作set 的比较对象,因为如果reference_wrapper 包装了可调用类型,则它是可调用的。

class MyClass
{
     typedef std::function<bool (int, int)> func_type;
public:
     MyClass(std::shared_ptr<func_type> const& f)
     : compare(f), set( std::ref(*f) )
     { }
private:
     std::shared_ptr<func_type> compare;
     std::set<int, std::reference_wrapper<func_type>> set;
};

reference_wrapper 不能为 null(如引用),因此您必须使用有效的 reference_wrapper 对象构造 std::set。

由于std::set 中的std::reference_wrapper 只是持有指向std::function 的非拥有指针,因此您需要在更新shared_ptr 的同时小心更新集合的比较对象,否则您可以删除对该函数的最后一个引用,因此shared_ptr 会破坏它,在集合中留下一个悬空指针。可以这样做:

void MyClass::replace_cmp(std::shared_ptr<func_type> const& f)
{
  set = std::set<int, std::reference_wrapper<func_type>>( std::ref(*f) );
  compare = f;
}

【讨论】:

  • 但是现在,我如何将它传递给 std::set?该集合需要一个可调用的类型,因此我需要使用 operator() 编写一个调用“比较”的类。正确的?还是有更短的方法?
  • 抱歉,我错过了那一点 :) 答案已更新 - reference_wrapper 提供 operator()
  • 所以reference_wrapper 只是调用它存储的shared_ptr 指向的std::function?还有一个关于性能的问题:如果 std::set 使用 operator() 获取一个对象,并使用默认 ctor 从它构造一个对象,它通常会很聪明并尝试内联函数吗? (因为在这种情况下在编译时就知道了)在我的情况下,函数是未知的,所以我失去了任何机会
  • std::reference_wrapper 存储了一个指向 std::function 的(非拥有)指针,该指针由 shared_ptr 拥有。在reference_wrapper 上调用operator() 会将呼叫转发到function。你说得对,std::set 通常能够内联比较,但 std::function 通常会阻止这种内联。
  • 如果您希望能够更改集合的顺序,请不要这样做。当集合的比较函数是其类型的一部分并且不能更改时,编译器很容易内联比较,例如std::set&lt;int, MyCmp&gt;,但是如果您希望能够替换比较,则需要间接调用,函数指针或std::function 或类似的,编译器内联间接调用要困难得多。我怀疑你会注意到性能问题,std::function 的良好实现仍然很快。
【解决方案2】:

你在你的constructor initializer list中告诉集合使用你的比较函数:

class MyClass
{
public:
    template<typename Fc>
    MyClass(Fc compare_func)
        : compare(compare_func),  // Initialize the comparison function
          set(compare)            // Tell the set to use out function for comparison
        {}
};

【讨论】:

    猜你喜欢
    • 2011-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    相关资源
    最近更新 更多