c++ - 在 lambda 中捕获完美转发的变量
c++ - capturing perfectly forwarded vars in a lambda
我是 C++ 的新手,目前正在为我的第一个项目编写控制反转容器,通过在基 类 上添加注册并将附加参数转发给构造函数来扩展 this blog post .
它现在工作得很好,但是当我多次实例化一个 lambda 时,捕获的值似乎被覆盖了。
示例:
struct A{
short a;
explicit A(short a_) : a(a_) {}
};
struct IC{
virtual unsigned C() = 0;
};
struct CImpl : public IC{
explicit CImpl(unsigned c_) : IC(), c(c_) {}
unsigned C() override{return c;}
private:
unsigned c;
};
template<class T, typename...TArgs>
std::function<T*()> AsMinimalAsItGets(TArgs&&...args)
{
return [&args...]() mutable -> T*
{
return new T(std::forward<TArgs>(args)...);
};
}
auto aFactory = AsMinimalAsItGets<A>(3);
auto cFactory = AsMinimalAsItGets<CImpl>(5);
auto aInst = aFactory();//aInst->a should be 3 but is 5
auto cInst = cFactory();//cInst->C() is 5
A 被实例化为 5 而不是 3。
我尝试了 this 作为解决方案,但没有解决问题。
那么如何在实例化 lambda 时正确捕获变量呢?
我需要以一种使我能够在 lambda
中使用完美转发的方式进行捕获
当您确实需要一份副本时,不要试图避免副本。在您的情况下,您尝试通过 std::forward
保留参数的值类别。但是当你 return 一个工厂函数 std::function<T*()>
时,这个闭包必须拥有它用来执行延迟构造的数据。否则,您最终会得到悬空引用,因为传递给 AsMinimalAsItGets
的参数仅比函数调用的范围长。
修复很简单:
template<class T, typename...TArgs>
std::function<T*()> AsMinimalAsItGets(TArgs&&...args)
{
return [args...]() mutable -> T*
// ^^^^^^^ (1) Copy the arguments into the closure
{
return new T(args...);
// ^^^^^^^ (2) Pass them as is to the ctor
};
}
请注意,正如@HolyBlackCat 指出的那样,这并不能完美地将参数 转发到 lambda 捕获。如所示,在C++20中,可以
return [...args = std::forward<TArgs>(args)]() mutable -> T*
{
return new T(args...);
};
在 C++17 中,您需要此解决方法:
return [args = std::make_tuple(std::forward<TArgs>(args)...)]() mutable -> T*
{
return std::apply([](auto&&... args){ return new T(args...); },
std::move(args));
};
我是 C++ 的新手,目前正在为我的第一个项目编写控制反转容器,通过在基 类 上添加注册并将附加参数转发给构造函数来扩展 this blog post .
它现在工作得很好,但是当我多次实例化一个 lambda 时,捕获的值似乎被覆盖了。
示例:
struct A{
short a;
explicit A(short a_) : a(a_) {}
};
struct IC{
virtual unsigned C() = 0;
};
struct CImpl : public IC{
explicit CImpl(unsigned c_) : IC(), c(c_) {}
unsigned C() override{return c;}
private:
unsigned c;
};
template<class T, typename...TArgs>
std::function<T*()> AsMinimalAsItGets(TArgs&&...args)
{
return [&args...]() mutable -> T*
{
return new T(std::forward<TArgs>(args)...);
};
}
auto aFactory = AsMinimalAsItGets<A>(3);
auto cFactory = AsMinimalAsItGets<CImpl>(5);
auto aInst = aFactory();//aInst->a should be 3 but is 5
auto cInst = cFactory();//cInst->C() is 5
A 被实例化为 5 而不是 3。
我尝试了 this 作为解决方案,但没有解决问题。
那么如何在实例化 lambda 时正确捕获变量呢? 我需要以一种使我能够在 lambda
中使用完美转发的方式进行捕获当您确实需要一份副本时,不要试图避免副本。在您的情况下,您尝试通过 std::forward
保留参数的值类别。但是当你 return 一个工厂函数 std::function<T*()>
时,这个闭包必须拥有它用来执行延迟构造的数据。否则,您最终会得到悬空引用,因为传递给 AsMinimalAsItGets
的参数仅比函数调用的范围长。
修复很简单:
template<class T, typename...TArgs>
std::function<T*()> AsMinimalAsItGets(TArgs&&...args)
{
return [args...]() mutable -> T*
// ^^^^^^^ (1) Copy the arguments into the closure
{
return new T(args...);
// ^^^^^^^ (2) Pass them as is to the ctor
};
}
请注意,正如@HolyBlackCat 指出的那样,这并不能完美地将参数 转发到 lambda 捕获。如
return [...args = std::forward<TArgs>(args)]() mutable -> T*
{
return new T(args...);
};
在 C++17 中,您需要此解决方法:
return [args = std::make_tuple(std::forward<TArgs>(args)...)]() mutable -> T*
{
return std::apply([](auto&&... args){ return new T(args...); },
std::move(args));
};