给定
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
销毁?为什么在前一个表达式中没有发生这种情况?
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");
}
(这个问题本来就长很多,看看历史就知道它来自哪里。)
最佳答案
这似乎是一个延长生命周期的问题。标准的相关引用如下:
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/