提问人:Komgcn 提问时间:9/15/2023 更新时间:9/15/2023 访问量:91
通过 std::ref 将shared_ptr传递给 std::bind 将剥离它的多态性
pass shared_ptr to std::bind through std::ref will strip it's polymorphism
问:
当我尝试将基类“this”指针dynamic_cast到派生类指针时,我的程序因 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的多态性,为什么会这样?
答:
3赞
pptaszni
9/15/2023
#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;
}
如果使用 ,调用将具有未定义的行为,因为超出范围并具有悬空引用,而不管它管理的整数是否仍与仍在作用域中的另一个shared_ptr共享。std::ref
f();
ptr
foo
评论
0赞
Komgcn
9/18/2023
感谢您的回复。确实,我在想什么......哈哈XD
3赞
Marek R
9/15/2023
#2
您有基本的悬空引用未定义行为。
这里:
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()));
- 放弃使用并使用 lambda:
std::bind
inner_.set_foo([this, base]() { run_foo(base.get()); });
评论
0赞
Komgcn
9/18/2023
感谢您的回复。是的,我肯定有一个指针悬空的问题。关于为什么代码看起来像这样,它是其他工作组中的代码,我想我会在解决我的问题之外留下它们
评论
base
在 的末尾超出范围,因此绑定对象对其持有的引用将悬空。如果您真的需要参考,请使用,但我认为在这种情况下没有理由使用参考。init
ref(bases_.back())
shared_ptr
bind