Dmi*_*ich 4 c++ lambda visual-c++ lazy-initialization c++11
我正在实现我自己的类,它提供了其成员的延迟初始化.我this在lambda中遇到了一种奇怪的捕获行为.
这是一个再现此错误的示例.
//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;
}
Run Code Online (Sandbox Code Playgroud)
变体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;
}
};
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:为什么这个例子会崩溃?捕获this内部构造函数是否有效?
通常,this在构造函数内捕获是有效的.但是当这样做时,你必须确保lambda不会比this它捕获的对象更活跃.否则,捕获的内容this会成为悬空指针.
这恰恰是你的情况.在Baz其this被捕获的是内部构造的临时main-scoped拉姆达(由创建的return Baz("123").然后,当Baz在内部产生Lazy<Baz>时,std::function从该临时移动Baz到Baz尖通过Lazy<Baz>::inst,但所捕获的this内部的移动拉姆达仍然指向原来,临时Baz对象.然后,该对象超出范围和重击,你有一个悬摆指针.
下面的Donghui Zhang的评论(使用enable_shared_from_this和捕获shared_ptr除了之外this)为您的问题提供了潜在的解决方案.您的Lazy<T>类将T实例存储为a拥有的实例std::unique_ptr<T>.如果您将仿函数签名更改为std::function<std::unique_ptr<T>()>,则将解决该问题,因为惰性初始化程序创建的对象将与存储的对象相同Lazy,因此捕获的对象this不会过早到期.