c++ - must - tags seo
Enlace a un valor débil (7)
¿Hay una manera de std :: bind to a std :: weak_ptr ? Me gustaría almacenar una devolución de llamada de "función débil" que se "desconecta" automáticamente cuando se destruye la llamada.
Sé cómo crear una función std :: utilizando un shared_ptr:
std::function<void()> MyClass::GetCallback()
{
return std::function<void()>(std::bind(&MyClass::CallbackFunc, shared_from_this()));
}
Sin embargo, la función std :: devuelta mantiene mi objeto vivo para siempre. Así que me gustaría enlazarlo a un débil_ptr :
std::function<void()> MyClass::GetCallback()
{
std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
return std::function<void()>(std::bind(&MyClass::CallbackFunc, thisWeakPtr));
}
Pero eso no compila. (std :: bind no aceptará weak_ptr!) ¿Hay alguna forma de enlazar a un weak_ptr?
He encontrado discusiones sobre esto (ver más abajo), pero parece que no hay una implementación estándar. ¿Cuál es la mejor solución para almacenar una "función débil", en particular si Boost no está disponible?
Discusiones / investigación (todas ellas utilizan Boost y no están estandarizadas):
- weak_function
- encuadernación débil
- Enlace "débil" (y una fix para ello)
- weak_fn
- Otra weak_fn
¿Qué tal esto? funciona solo para acciones std::function<void()>
pero quizás se pueda generalizar para funciones parametrizadas arbitrariamente.
#include <memory>
#include <functional>
template<typename T>
void
perform_action_or_ignore_when_null(
std::weak_ptr<T> weak,
std::function< void( std::shared_ptr<T> ) > func
)
{
if(auto ptr = weak.lock())
func(ptr);
}
template<typename T>
std::function<void()>
ignore_when_null(
std::weak_ptr<T> weak,
std::function< void( std::shared_ptr<T> ) > func
)
{
return std::bind(perform_action_or_ignore_when_null<T>, weak, func);
}
Aquí hay un ejemplo de uso:
struct Foo {
Foo() {}
void bar() {
std::cout << "hello world!" << std::endl;
}
};
void main()
{
std::weak_ptr<Foo> weakfoo;
std::function<void(std::shared_ptr<Foo>)> foobar = std::bind(&Foo::bar, std::placeholders::_1);
{
auto foo = std::make_shared<Foo>();
weakfoo = foo;
auto f = ignore_when_null(weakfoo, foobar);
f(); // prints "hello world!";
}
auto g = ignore_when_null(weakfoo, foobar);
g(); // does nothing
}
No estoy seguro de por qué esa definición no está en alza. Debe haber una buena razón (¿cómo hacer frente al fallo de bloqueo? ¿Es aceptable tirar desde allí? ¿Seguridad para el hilo?) De todos modos, eso validará a la persona que llama.
namespace boost {
template<class T> T * get_pointer(boost::weak_ptr<T> const& p)
{
boost::shared_ptr< T > _strong = p.lock();
if( _strong )
return _strong.get();
else
throw 1;
}
}
int main(int arg, char *argv[])
{
boost::weak_ptr< MyType > weak_bad;
{
boost::shared_ptr< MyType > strong(new MyType);
boost::weak_ptr< MyType > weak(strong);
boost::function< void(int) > _func1 = boost::bind(&MyType::setX, weak, _1);
_func1(10);
weak_bad = strong;
}
try {
boost::function< void(int) > _func1 = boost::bind(&MyType::setX, weak_bad, _1);
_func1(10);
}
catch(...)
{
std::cout << "oops!";
}
return 0;
};
Otra solución:
Podrías envolver la función std ::. La clase que produce la devolución de llamada mantendrá un shared_ptr <wrapper_type> y proporcionará un weak_ptr <wrapper_type>. El objeto productor sería el que tiene la propiedad, si se sale del alcance, las personas que llaman no podrán promover su débil referencia. Su tipo de envoltorio podría reenviar argumentos de llamada a la función std :: o simplemente exponerlo a través de su interfaz. Solo asegúrese de que en la copia usted maneje adecuadamente el shared_ptr en el contenedor (no lo comparta).
template< typename _Ty >
struct wrapper
{
wrapper(_Ty wrappe)
: _wrappe(wrappe)
{ }
_Ty _wrappe;
};
...
boost::shared_ptr< wrapper < std::func< ... > > _func(new wrapper < std::func< ... > );
...
boost::weak_ptr< wrapper < std::func< ... > getCallBack() {
return _func;
}
Pude crear puntos débiles de la función std :: y lo probé con clang-3.2 (no dio ninguna restricción del compilador).
Aquí hay una aplicación de muestra que crea y prueba lo que creo que estás pidiendo:
#include <functional>
#include <memory>
#include <iostream>
typedef std::function<void(void)> Func;
typedef std::shared_ptr<Func> SharedFunc;
typedef std::weak_ptr<Func> WeakFunc;
void Execute( Func f ) {
f();
}
void Execute( SharedFunc sf ) {
(*sf)();
}
void Execute( WeakFunc wf ) {
if ( auto f = wf.lock() )
(*f)();
else
std::cout << "Your backing pointer went away, sorry./n";
}
int main(int, char**) {
auto f1 = [](){ std::cout << "Func here./n"; };
Execute( f1 );
auto f2 = [](){ std::cout << "SharedFunc here./n"; };
SharedFunc sf2( new Func(f2) );
Execute( sf2 );
auto f3 = [](){ std::cout << "WeakFunc here./n"; };
SharedFunc sf3( new Func(f3) );
WeakFunc wf3( sf3 );
Execute( wf3 );
// Scoped test to make sure that the weak_ptr is really working.
WeakFunc wf4;
{
auto f4 = [](){ std::cout << "You should never see this./n"; };
SharedFunc sf4( new Func(f4) );
wf4 = sf4;
}
Execute( wf4 );
return 0;
}
La salida fue:
~/projects/stack_overflow> clang++-mp-3.2 --std=c++11 --stdlib=libc++ weak_fun.cpp -o wf && ./wf
Func here.
SharedFunc here.
WeakFunc here.
Your backing pointer went away, sorry.
Puede enlazar weak_ptr
a la función como uno de los parámetros,
y verifíquelo cuando se llame la función.
Por ejemplo:
std::function<void()> MyClass::GetCallback()
{
std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
return std::function<void()>(std::bind(&MyClass::CallbackFunc, this,
thisWeakPtr));
}
void MyClass::CallbackFunc(const std::weak_ptr<MyClass>& thisWeakPtr)
{
if (!thisWeakPtr.lock()) {
return;
}
// Do your callback job.
// ...
}
Sé que esta es una pregunta antigua, pero tengo el mismo requisito y estoy seguro de que no estoy solo.
La solución al final para mí fue devolver un objeto de función que devuelva un boost :: opcional <> dependiendo de si la función fue llamada o no.
código aquí:
#include <boost/optional.hpp>
#include <memory>
namespace value { namespace stdext {
using boost::optional;
using boost::none;
struct called_flag {};
namespace detail
{
template<class Target, class F>
struct weak_binder
{
using target_type = Target;
using weak_ptr_type = std::weak_ptr<Target>;
weak_binder(weak_ptr_type weak_ptr, F f)
: _weak_ptr(std::move(weak_ptr))
, _f(std::move(f))
{}
template<class...Args,
class Result = std::result_of_t<F(Args...)>,
std::enable_if_t<not std::is_void<Result>::value>* = nullptr>
auto operator()(Args&&...args) const -> optional<Result>
{
auto locked_ptr = _weak_ptr.lock();
if (locked_ptr)
{
return _f(std::forward<Args>(args)...);
}
else
{
return none;
}
}
template<class...Args,
class Result = std::result_of_t<F(Args...)>,
std::enable_if_t<std::is_void<Result>::value>* = nullptr>
auto operator()(Args&&...args) const -> optional<called_flag>
{
auto locked_ptr = _weak_ptr.lock();
if (locked_ptr)
{
_f(std::forward<Args>(args)...);
return called_flag {};
}
else
{
return none;
}
}
weak_ptr_type _weak_ptr;
F _f;
};
}
template<class Ret, class Target, class...FuncArgs, class Pointee, class...Args>
auto bind_weak(Ret (Target::*mfp)(FuncArgs...), const std::shared_ptr<Pointee>& ptr, Args&&...args)
{
using binder_type = decltype(std::bind(mfp, ptr.get(), std::forward<Args>(args)...));
return detail::weak_binder<Target, binder_type>
{
std::weak_ptr<Target>(ptr),
std::bind(mfp, ptr.get(), std::forward<Args>(args)...)
};
}
}}
Llamado (por ejemplo) así:
TEST(bindWeakTest, testBasics)
{
struct Y
{
void bar() {};
};
struct X : std::enable_shared_from_this<X>
{
int increment(int by) {
count += by;
return count;
}
void foo() {
}
Y y;
int count = 0;
};
auto px = std::make_shared<X>();
auto wf = value::stdext::bind_weak(&X::increment, px, std::placeholders::_1);
auto weak_call_bar = value::stdext::bind_weak(&Y::bar, std::shared_ptr<Y>(px, &px->y));
auto ret1 = wf(4);
EXPECT_TRUE(bool(ret1));
EXPECT_EQ(4, ret1.get());
auto wfoo1 = value::stdext::bind_weak(&X::foo, px);
auto retfoo1 = wfoo1();
EXPECT_TRUE(bool(retfoo1));
auto retbar1 = weak_call_bar();
EXPECT_TRUE(bool(retbar1));
px.reset();
auto ret2 = wf(4);
EXPECT_FALSE(bool(ret2));
auto retfoo2 = wfoo1();
EXPECT_FALSE(bool(retfoo2));
auto retbar2 = weak_call_bar();
EXPECT_FALSE(bool(retbar2));
}
El código fuente y las pruebas están disponibles aquí: https://github.com/madmongo1/valuelib
#include <iostream>
#include <string>
#include <memory>
#include <functional>
using namespace std;
template < typename T > class LockingPtr {
std :: weak_ptr < T > w;
public:
typedef shared_ptr < T > result_type;
LockingPtr ( const std :: shared_ptr < T > & p ) : w ( p ) { }
std :: shared_ptr < T > lock ( ) const {
return std :: shared_ptr < T > ( w );
}
std :: shared_ptr < T > operator-> ( ) const {
return lock ( );
}
template < typename ... Args > std :: shared_ptr < T > operator( ) ( Args ... ) const {
return lock ( );
}
};
template < typename T > LockingPtr < T > make_locking ( const shared_ptr < T > & p ) {
return p;
}
namespace std {
template < typename T > struct is_bind_expression < LockingPtr < T > > :
public true_type { };
}
int main() {
auto p = make_shared < string > ( "abc" );
auto f = bind ( & string :: c_str, make_locking ( p ) );
cout << f ( ) << ''/n'';
p.reset ( );
try {
cout << f ( ) << ''/n'';
} catch ( const exception & e ) {
cout << e.what ( ) << ''/n'';
}
// your code goes here
return 0;
}
salida:
abc
bad_weak_ptr
std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
return std::function<void()>(std::bind(&MyClass::CallbackFunc, thisWeakPtr));
Nunca debes hacer esto. Siempre.
MyClass::CallbackFunc
es una función miembro no estática de la clase MyClass
. Al ser una función miembro no estática, se debe llamar con una instancia válida de MyClass
.
El punto entero de weak_ptr
es que no es necesariamente válido. Puede detectar su validez transformándola en un shared_ptr
y luego probando si el puntero es NULL. Debido a que weak_ptr
no se garantiza que sea válido en todo momento, no puede llamar a una función miembro no estática con uno.
Lo que estás haciendo no es más válido que:
std::bind(&MyClass::CallbackFunc, nullptr)
Puede compilarse, pero eventualmente se bloqueará cuando intentes llamarla.
Su mejor apuesta es usar la lógica real, para no llamar a la función de devolución de llamada si weak_ptr
no es válido. bind
no está diseñado para hacer lógica; simplemente hace exactamente lo que usted le dice: llamar a la función. Así que necesitas usar un lambda apropiado:
std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
return std::function<void()>([thisWeakPtr]()
{
auto myPtr = thisWeakPtr.lock();
if(myPtr)
myPtr->CallbackFunc()
});