c++ - 了解使用 hana::compose 和 shared_ptr 时的内存管理问题

标签 c++ functional-programming c++17 smart-pointers boost-hana

给定

  • makeDocument 函数创建临时资源并通过 std::share_ptr 移交它,
  • 封装默认 deref 运算符应用的 * 函数,
  • 和消费者 print 函数,
  • boost::hana::compose

我注意到了

compose(print, deref, makeDocument)("good");          // OK
compose(print, compose(deref, makeDocument))("bad");  // UB

我想了解为什么。具体来说,为什么后一个表达式会导致在将其指针对象传递给 std::shared_ptr 并由 deref 处理之前将传递给 print 的临时 hana::compose 销毁?为什么在前一个表达式中没有发生这种情况?

(CompilerExplorer example。)


I've managed to strip ojit_code down to the minimum I need for my example:

#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>

template <typename F, typename G>
struct compose {
    F f; G g;
    compose(F f, G g) : f{f}, g{g} {}

    template <typename X>
    decltype(auto) operator()(X const& x) const& {
        return f(g(x));
    }
};

struct Document {
    std::string str;
    Document(std::string&& str) : str{std::move(str)} {}
    ~Document() {
        str = "bad";
    };
};

void print(Document const& doc1) {
    std::cout << doc1.str << std::endl;
}

auto deref = [](auto&& x) -> decltype(auto) {
    return *std::forward<decltype(x)>(x);
};

auto makeDocument = [](std::string&& str) {
    return std::make_shared<Document>(std::move(str));
};

const auto good = compose(compose(print, deref), makeDocument);
const auto bad = compose(print, compose(deref, makeDocument));

int main() {
    good("good");
    bad("good");
}

(这个问题本来就长很多,看看历史就知道它来自哪里。)

最佳答案

这似乎是一个延长生命周期的问题。标准的相关引用如下:

[class.temporary]

6 The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:

  • a temporary materialization conversion (7.3.4),
  • [...]
  • a class member access (7.6.1.4) using the . operator where the left operand is one of these expressions and the right operand designates a non-static data member of non-reference type,
  • [...]

The exceptions to this lifetime rule are:

  • A temporary object bound to a reference parameter in a function call (7.6.1.2) persists until the completion of the full-expression containing the call.
  • [...]
  • The lifetime of a temporary bound to the returned value in a function return statement (8.7.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.

有了这个,您的示例可以进一步简化为以下代码片段 ( godbolt ):

auto&& good = std::make_shared<Document>("good");
std::cout<<(*good).str<<std::endl;

auto&& bad = *std::make_shared<Document>("good");
std::cout<<bad.str<<std::endl;

在上面的 block 中,good是对共享指针的右值引用,因此确实延长了生命周期,直到调用 std::cout (或者在您的示例中,对于 compose(print, deref) 的整个执行)。

另一方面,bad是对成员函数 operator* 返回的引用的引用,这会导致 shared_ptr 立即被破坏根据上面引用中的最后一个要点。

请注意,如果shared_ptr 中有一个成员可以显式绑定(bind)到引用(例如 auto&& r = std::make_shared<Document>("good").wrapped_pointer ),则生命周期扩展将再次应用。这些事情在this article中有进一步解释。 ,其中明确建议反对使用此类依赖于生命周期延长的构造。在您的示例中,可以通过从 deref 返回按值来简单地解决这个问题。 .

关于c++ - 了解使用 hana::compose 和 shared_ptr 时的内存管理问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76135231/

相关文章:

c++ - 为什么 std::vector 不能保存常量对象?

c++ - 从内存地址获取源代码中的函数

javascript - lodash:组合对象数组

c++ - 新的 std::map::erase() 签名 C++17

c++ - 为浮点常量调用伪析构函数的有效语法

c++ - 使用指令和部分特化

c++ - 如何与 USB 扫描仪通信?

functional-programming - 如何将数据连接到 react-router-relay 中的深层组件?

javascript - 广义 Curry - Javascript

c++ - 我可以有 const 参数包吗?