通过 std::ref 将共享指针传递给 std::bind 将剥离它的多态性

问题描述 投票:0回答:2

当我尝试将基类“this”指针动态转换为派生类指针时,我的程序因 SIGSEGV 崩溃。我想知道它是否与 std::bind 和 std::ref 有关。下面是我的示例代码,它可能会产生问题。

// header h.hpp

#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <vector>

using namespace std;

template <typename T> 
class Derived;

class Base {
public:
  virtual ~Base() {}

  template <typename T> 
  void foo(const T &t) {
    cout << "Base foo\n";
    auto derived = dynamic_cast<Derived<T> *>(this); // SIGSEGV crash!
    derived->foo(t);
  }

private:
};

template <typename T> 
class Derived : public Base {
public:
  void foo(const T &t) { cout << "Derived foo\n"; }

private:
};

class Outter {
public:
  void init();
  void run();

private:
  void run_foo(shared_ptr<Base> &base);

  class Inner {
  public:
    void run() {
      for (const auto &f : foos_) {
        f();
      }
    }

    void set_foo(function<void()> f) { 
        foos_.push_back(f); 
    }

  private:
    static vector<function<void()>> foos_;
  };

  Inner inner_;
  vector<shared_ptr<Base>> bases_;
};
// source main.cpp

#include "h.hpp"

vector<function<void()>> Outter::Inner::foos_;

void Outter::init() {
  shared_ptr<Base> base = make_shared<Derived<int>>();
  bases_.push_back(base);
  
//   inner_.set_foo(bind(&Outter::run_foo, this, base)); // no ref version
  inner_.set_foo(bind(&Outter::run_foo, this, ref(base))); // ref verison
}

void Outter::run() { inner_.run(); }

void Outter::run_foo(shared_ptr<Base> &base) {
  int t = 123;
  base->foo(t);
}

int main() {
  Outter outter;
  outter.init();
  outter.run();
  return 0;
}

如果我注释掉 ref 版本并使用“no-ref”版本,则程序运行良好并且不会中断。看起来 std::ref 函数正在砍掉 Base shared_ptr 的多态性,为什么会这样?

c++ templates bind ref dynamic-cast
2个回答
1
投票

shared_ptr
或多态性无关,你的
shared_ptr<Base> base
只是超出了范围。

考虑这个例子:

void foo(std::shared_ptr<int>& ptr)
{
    std::cout << *ptr << std::endl;
}

int main()
{
    std::function<void()> f;
    std::shared_ptr<int> backup;
    {
        std::shared_ptr<int> ptr{new int{7}};
        backup = ptr;
        f = std::bind(foo, std::ref(ptr));
        // f = std::bind(foo, ptr);
    }
    f();
    return 0;
}

如果使用

std::ref
,调用
f();
将具有未定义的行为,因为
ptr
超出了范围,并且
foo
具有悬空引用,无论它管理的整数仍然与另一个共享指针共享范围内。

带有 addr sanitizer 的示例


0
投票

您有基本的悬空参考未定义行为。

这里:

void Outter::init() {
  shared_ptr<Base> base = make_shared<Derived<int>>();
  bases_.push_back(base);
  
//   inner_.set_foo(bind(&Outter::run_foo, this, base)); // no ref version
  inner_.set_foo(bind(&Outter::run_foo, this, ref(base))); // ref verison
}

当该函数结束局部变量的生命周期时

base
结束。 这意味着
foos_
包含
std::function
,它使用对生命周期结束的对象的引用。

主要问题是:

void Outter::run_foo(shared_ptr<Base> &base) 

为什么你需要在这里引用智能指针?
此函数不会对其参数执行任何类型的所有权转移,因此应使用适当的 API,指向

Base
的原始指针或对
Base
的引用(取决于您的编码约定):

void Outter::run_foo(Base* base)

之后有两种方法可以修复它:

  • 如果你可以为
    base
    所指向的对象的生命周期提供保证,你可以只使用原始指针:
inner_.set_foo(bind(&Outter::run_foo, this, base.get()));
  • 放弃使用
    std::bind
    并使用 lambda:
inner_.set_foo([this, base]() { run_foo(base.get()); });
© www.soinside.com 2019 - 2024. All rights reserved.