Lambda的“this”捕获返回垃圾

时间:2016-10-14 12:49:12

标签: c++ c++11 visual-c++ lambda lazy-initialization

我正在实现我自己的类,它提供了其成员的延迟初始化。我在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是否有效?

2 个答案:

答案 0 :(得分:8)

通常,在构造函数中捕获this是有效的。但是当这样做时,你必须确保lambda不会比它捕获this的对象更长。否则,捕获的this成为悬空指针。

这正是您案件中发生的事情。捕获Baz的{​​{1}}是this范围内的lambda(由main创建的临时构造的临时构造。然后,当创建return Baz("123")时在Baz内,Lazy<Baz>从临时std::function移动到Baz所指向的Baz但被捕获的Lazy<Baz>::inst在里面移动的lambda仍然指向原始的临时this对象。那个对象超出范围而 wham ,你有一个悬空指针。

以下Donghui Zhang的评论(使用Baz并在enable_shared_from_this之外捕获shared_ptr)为您的问题提供了潜在的解决方案。您的this类将Lazy<T>个实例存储为T所拥有的实例。如果您将仿函数签名更改为std::unique_ptr<T>,您将解决该问题,因为延迟初始化程序创建的对象将与std::function<std::unique_ptr<T>()>中存储的对象相同,因此捕获的{ {1}}不会过早到期。

答案 1 :(得分:0)

问题是捕获的this是一个特定的对象。您复制lambda而不更改捕获的thisLazy然后悬挂,你的代码就会中断。

您可以使用智能指针来管理它;但你可能想改变它。

我会修改T。 Lazy需要以及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; 有一个

getStr

调整ctor和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"); } }; 来自无源数据:

fping -i 300 -r 5 -f hosts.txt