Lambda 的"this"捕获返回垃圾
Lambda's "this" capture returns garbage
我正在实现自己的类,它提供其成员的延迟初始化。我遇到了一个奇怪的行为,在lambda中捕获this
。
下面是一个重现这个错误的例子。
//Baz.h
#include <memory>
#include <functional>
#include "Lazy.hpp"
struct Foo
{
std::string str;
Foo() = default;
Foo(std::string str) : str(str) {}
Foo(Foo&& that) : str(that.str) { }
};
class Baz
{
std::string str;
Lazy<std::unique_ptr<Foo>> foo;
public:
Baz() = default;
Baz(const std::string& str) : str(str)
{
//lazy 'this->foo' initialization.
//Is capturing of 'this' valid inside ctors???.
this->foo = { [this] { return buildFoo(); } };
}
Baz(Baz&& that) : foo(std::move(that.foo)), str(that.str) { }
std::string getStr() const
{
return this->foo.get()->str;
}
private:
std::unique_ptr<Foo> buildFoo()
{
//looks like 'this' points to nothing here.
return std::make_unique<Foo>(str); //got error on this line
}
};
int _tmain(int argc, _TCHAR* argv[])
{
///Variant 1 (lazy Foo inside regular Baz):
Baz baz1("123");
auto str1 = baz1.getStr();
///Variant 2 (lazy Foo inside lazy Baz):
Lazy<Baz> lazy_baz = { [](){ return Baz("123"); } };
auto& baz2 = lazy_baz.get(); //get() method returns 'inst' member (and initialize it if it's not initialized) see below
auto str2 = baz2.getStr();
return 0;
}
变量1工作良好。
变种2崩溃,出现以下错误:
lambda_this_capture_test.exe中0x642DF4CB (msvcr120.dll)的未处理异常:0xC0000005:访问冲突读取位置0x00E0FFFC.
我使用vc++120编译器(从VS2013)。
这是我的简化Lazy
类:
#pragma once
#include <memory>
#include <atomic>
#include <mutex>
#include <functional>
#include <limits>
template<
class T,
typename = std::enable_if_t<
std::is_move_constructible<T>::value &&
std::is_default_constructible<T>::value
>
>
class Lazy
{
mutable std::unique_ptr<T> inst;
std::function<T(void)> func;
mutable std::atomic_bool initialized;
mutable std::unique_ptr<std::mutex> mutex;
public:
Lazy()
: mutex(std::make_unique<std::mutex>())
, func([]{ return T(); })
{
this->initialized.store(false);
}
Lazy(std::function<T(void)> func)
: func(std::move(func))
, mutex(std::make_unique<std::mutex>())
{
this->initialized.store(false);
}
//... <move ctor + move operator>
T& get() const
{
if (!initialized.load())
{
std::lock_guard<std::mutex> lock(*mutex);
if (!initialized.load())
{
inst = std::make_unique<T>(func());
initialized.store(true);
}
}
return *inst;
}
};
所以我的问题是:为什么这个例子会崩溃?它是有效的捕获this
内部构造器?
一般来说,在构造函数中捕获this
是有效的。但是这样做时,您必须确保lambda不会比它捕获的this
的对象活得更久。否则,捕获的this
将变成悬空指针。
这正是在你的情况下发生的事情。捕获this
的Baz
是main
作用域内的临时构造(由return Baz("123")
创建)。然后,当在Lazy<Baz>
内部创建Baz
时,std::function
从临时Baz
移动到Lazy<Baz>::inst
所指向的Baz
, ,但是在移动lambda的内部捕获的this
仍然指向原始的临时Baz
对象。这个对象就会超出作用域,然后砰的一声,你就有了一个悬浮指针。
下面张东辉的评论(使用enable_shared_from_this
并在this
之外捕获shared_ptr
)为您的问题提供了一个潜在的解决方案。Lazy<T>
类将T
实例存储为std::unique_ptr<T>
所拥有。如果将函子签名更改为std::function<std::unique_ptr<T>()>
,就可以解决这个问题,因为惰性初始化器创建的对象将与存储在Lazy
中的对象相同,因此捕获的this
不会过早过期。
问题是捕获的this
是一个特定的对象。在不更改捕获的this
的情况下复制lambda。然后this
悬挂,你的代码中断。
你可以使用智能指针来管理这个;但是你可能想要重新定义它。
我会修改Lazy
。Lazy需要源代码和T
。
我想给它一个签名。
template<
class Sig, class=void
>
class Lazy;
template<
class T,
class...Sources
>
class Lazy<
T(Sources...),
std::enable_if_t<
std::is_move_constructible<T>::value &&
std::is_default_constructible<T>::value
>
>
{
std::function<T(Sources...)> func;
// ...
Lazy(std::function<T(Sources...)> func)
// ...
T& get(Sources...srcs) const {
// ...
inst = std::make_unique<T>(func(std::forward<Sources>(srcs)...));
// ...
现在Baz
有一个
Lazy<std::unique_ptr<Foo>(Baz const*)> foo;
与调整的ctor和getStr
:
Baz(const std::string& str) : str(str)
{
this->foo = { [](Baz const* baz) { return baz->buildFoo(); } };
}
std::string getStr() const
{
return this->foo.get(this)->str;
}
,在main
中,我们声明Baz
来自非源数据:
Lazy<Baz()> lazy_baz = { []{ return Baz("123"); } };
- 在 C++ 中返回新的构造函数(*this)工厂
- 为什么在通过引用返回运算符分配时取消引用'this'指针?
- 是否是从等待返回到悬而未决的"this"实例的未定义行为?
- 返回 *this 和 this - c++ 之间的区别
- 返回C++"This"对象的函数
- 返回带或不带 *this 的变量的确切方式不同 |这不是关于*此指针|正确性
- 返回 *this,尝试级联,并且不返回引用
- 从 const 成员函数返回 'this' 作为非常量
- 为什么有些类方法返回"*this"(self的对象引用)?
- C++对返回*this的成员函数的重复调用顺序
- 如何在php扩展中返回$this
- 为什么当这仍在传递时需要返回 *this
- 在类赋值操作符函数中,为什么必须返回*this ?
- 宏返回'this'指针,或 NULL (当指针不可用时)
- 继承返回*this的成员函数
- 返回this的右值引用的正确方法
- 在C++返回"this"?
- 在返回*this后调用析构函数
- 我可以有一个返回*this并处理非常量对象的const成员函数吗
- 如何在托管C++中正确实现类返回"this"的方法?