【问题标题】:Virtual functions and std::function?虚函数和 std::function?
【发布时间】:2018-06-07 18:59:56
【问题描述】:

考虑以下 C++17 中的代码:

#include <iostream>
#include <functional>

struct base
{
    base() {std::cout << "base::base" << std::endl;}
    virtual ~base() {std::cout << "base::~base" << std::endl;}
    virtual void operator()() {std::cout << "base::operator()" << std::endl;}
};

struct derived1: base
{
    derived1() {std::cout << "derived1::derived1" << std::endl;}
    virtual ~derived1() {std::cout << "derived1::~derived1" << std::endl;}
    virtual void operator()() {std::cout << "derived1::operator()" << std::endl;}
};

struct derived2: base
{
    derived2() {std::cout << "derived2::derived2" << std::endl;}
    virtual ~derived2() {std::cout << "derived2::~derived2" << std::endl;}
    virtual void operator()() {std::cout << "derived2::operator()" << std::endl;}
};

int main(int argc, char* argv[])
{
    base* ptr1 = new derived1();
    base* ptr2 = new derived2();
    std::function f1(*ptr1);
    std::function f2(*ptr2);
    std::invoke(*ptr1);     // calls derived1::operator()
    std::invoke(*ptr2);     // calls derived2::operator()
    std::invoke(f1);        // calls base::operator()
    std::invoke(f2);        // calls base::operator()
    delete ptr1;
    delete ptr2;
    return 0;
}

std::function 似乎没有用虚函数做正确的事情。有什么方法可以使std::invoke(*ptrN)std::invoke(fN) 的行为方式相同吗?或者有什么方法可以创建一个新的函数包装器来处理虚函数?

【问题讨论】:

  • std::function 复制它接收到的函子。你传给它base
  • What is object slicing?的可能重复
  • 你也应该更喜欢std::unique_ptr
  • 我不相信它是重复的。 “为什么”不是帖子的重点。重点是如何让它发挥作用。
  • 你用什么编译的? (编译器/版本)

标签: c++ oop templates c++17 virtual-functions


【解决方案1】:

您可以使用std::reference_wrapper,或方便的std::refstd::function 在这种情况下将使用 SOO(小对象优化),因此不会复制/移动对象(避免 slicing problem)。但是,您不会获得演绎指南,因此您需要指定模板参数。

std::function<void()> f1(std::ref(*ptr1));
std::function<void()> f2(std::ref(*ptr2));

【讨论】:

  • 避免切片不是通过小对象优化,而是做它声称要做的事情:优化。
  • 为什么推导指南因 std::reference_wrapper 失败?
  • @Vincent - 因为引用包装没有 operator()
  • @Vincent - 一个模板转发。推导指南依赖于operator() 作为成员函数,而不是一个模板。粗略地说。
【解决方案2】:

是否有任何方法可以使 std::invoke(*ptrN) 和 std::invoke(fN) 的行为方式相同?或者有什么方法可以创建一个新的函数包装器来处理虚函数?

std::function 复制其参数以便稍后运行,正如问题的 cmets 中已建议的那样。
因此,您可以避免切片对象并复制能够正确处理多态性的其他内容。
举个例子:

std::function f1([ptr1](){ (*ptr1)(); });
std::function f2([ptr2](){ (*ptr2)(); });

【讨论】:

    【解决方案3】:

    您的代码没有将派生类的实例传递给std::function,而是通过复制派生的实例来构造新的基对象,该实例传递给std::function

    【讨论】:

      【解决方案4】:

      避免std::function 进行切片复制的一种简单方法是将方法operator()绑定到它应该应用的对象。

      所以你可以写:

      auto f1 = std::bind(&base::operator(), ptr1);
      auto f2 = std::bind(&base::operator(), ptr2);
      std::invoke(f1);                          // calls derived1::operator()
      std::invoke(f2);                          // calls derived2::operator()
      

      【讨论】:

      • Invoke 可以很好地“调用 ptr1 指向的对象”(它会自动调用operator(),在示例中也显示了这一点)。真正的问题是std::function 创建了该对象的切片副本——它存储了base,因为传入的类型是base。你只需要避免那个副本。有几种方法可以做到这一点:std::bind()std::ref() 和使用 lambda 都是可以接受的答案。
      • @oisyn:我对这些语义太老了 :-( 我已经编辑了我的帖子...谢谢您的反馈。
      猜你喜欢
      • 2021-09-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-02
      • 2023-03-31
      相关资源
      最近更新 更多