我想将unique_ptr移至其对象的方法:
class Foo {
void method(std::unique_ptr<Foo>&& self) {
// this method now owns self
}
}
auto foo_p = std::make_unique<Foo>();
foo_p->method(std::move(foo_p));
这可以编译,但是我不知道这是否不是未定义的行为。由于我从对象移出时还对其调用了方法。
是UB吗?
如果是这样,我可以用以下方法修复它:
auto raw_foo_p = foo_p.get();
raw_foo_p->method(std::move(foo_p))
对吗?
(可选动机:)
传递对象以延长其寿命。它会存在于lambda中,直到lambda被异步调用。 (boost :: asio)
请先参见Server::accept
,然后再参见Session::start
。
您可以看到原始的实现使用了shared_ptr,但是我不明白为什么这样做是合理的,因为我只需要Session对象的一个所有者即可。
Shared_ptr使代码更加复杂,当我不熟悉shared_ptr时,我很难理解。
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
using namespace boost::system;
using namespace boost::asio;
using boost::asio::ip::tcp;
class Session /*: public std::enable_shared_from_this<Session>*/ {
public:
Session(tcp::socket socket);
void start(std::unique_ptr<Session>&& self);
private:
tcp::socket socket_;
std::string data_;
};
Session::Session(tcp::socket socket) : socket_(std::move(socket))
{}
void Session::start(std::unique_ptr<Session>&& self)
{
// original code, replaced with unique_ptr
// auto self = shared_from_this();
socket_.async_read_some(buffer(data_), [this/*, self*/, self(std::move(self))] (error_code errorCode, size_t) mutable {
if (!errorCode) {
std::cout << "received: " << data_ << std::endl;
start(std::move(self));
}
// if error code, this object gets automatically deleted as `self` enters end of the block
});
}
class Server {
public:
Server(io_context& context);
private:
tcp::acceptor acceptor_;
void accept();
};
Server::Server(io_context& context) : acceptor_(context, tcp::endpoint(tcp::v4(), 8888))
{
accept();
}
void Server::accept()
{
acceptor_.async_accept([this](error_code errorCode, tcp::socket socket) {
if (!errorCode) {
// original code, replaced with unique_ptr
// std::make_shared<Session>(std::move(socket))->start();
auto session_ptr = std::make_unique<Session>(std::move(socket));
session_ptr->start(std::move(session_ptr));
}
accept();
});
}
int main()
{
boost::asio::io_context context;
Server server(context);
context.run();
return 0;
}
编译为:
g++ main.cpp -std=c++17 -lpthread -lboost_system
答案 0 :(得分:4)
对于您的第一个代码块:
std::unique_ptr<Foo>&& self
是一个引用,并为其分配一个参数std::move(foo_p)
,其中foo_p
是一个命名为std::unique_ptr<Foo>
的引用,只会将引用self
绑定到{{1 }},这意味着foo_p
将在调用范围内引用self
。
它不会创建任何新的foo_p
对象,托管std::unique_ptr<Foo>
对象的所有权可以转移到该位置。没有移动构造或分配发生,并且Foo
对象仍然被调用范围中的Foo
破坏了。
因此,尽管您可以以可能导致体内未定义行为的方式使用引用foo_p
,但此函数调用本身没有未定义行为的风险。
也许您打算让self
成为self
而不是std::unique_ptr<Foo>
。在那种情况下,std::unique_ptr<Foo>&&
不是参考,但是如果通过self
进行调用,则托管对象Foo
的所有权将通过移动构造传递到该对象,并且在std::move(p_foo)
中的函数调用以及托管的foo_p->method(std::move(foo_p))
。
此替代变体本身是否可能是不确定的行为,取决于所使用的C ++标准版本。
在C ++ 17之前,允许编译器选择在评估Foo
之前评估调用的参数(以及参数的关联移动结构)。这意味着foo_p->method
可能已经从评估foo_p
开始,从而导致未定义的行为。可以像您建议的方式进行修复。
从C ++ 17开始,可以保证在调用的任何参数之前对postfix-expression(此处为foo_p->method
)进行求值,因此,调用本身不会成为问题。 (仍然会引起其他问题。)
对于后一种情况的详细说明:
foo_p->method
被解释为foo_p->method
,因为(foo_p->operator->())->method
提供了此std::unique_ptr
。 operator->()
将解析为指向(foo_p->operator->())
管理的Foo
对象的指针。最后一个std::unique_ptr
解析为该对象的成员函数->method
。在C ++ 17中,此评估在对method
的参数进行任何评估之前进行,因此是有效的,因为尚未发生从method
的移动。
然后,根据设计,参数的评估顺序不确定。因此, A)可能会由于初始化参数而将{_1}之前的unique_ptr foo_p
移开。并且 B),它将 从foo_p
运行并使用初始化的this
时移开。
但是 A)并不是问题,因为第8.2.2:4节符合预期:
如果该函数是非静态成员函数,则该函数的 this 参数应使用指向调用对象的指针进行初始化
(而且我们知道在 任何参数被求值之前,该对象已解决。)
和 B)无关紧要:({another question)
C ++ 11规范保证将对象的所有权从一个unique_ptr转移到另一个unique_ptr不会更改对象本身的位置
第二段:
method
创建类型为this
(不是引用)的lambda捕获,该捕获使用引用self(std::move(self))
初始化,引用{{1}中的lambda引用了std::unique_ptr<Session>
}}。通过移动构造,self
对象的所有权从session_ptr
转移到lambda的成员。
然后将lambda传递到accept
,这将(由于未将lambda作为非常量左值引用传递)将lambda移入内部存储,以便以后可以异步调用。通过此举,Session
对象的所有权也转移到了boost :: asio内部。
session_ptr
立即返回,因此async_read_some
的所有局部变量和Session
中的lambda被销毁。但是async_read_some
的所有权已经转移,因此这里没有生命周期问题,因此没有未定义的行为。
将异步调用lambda的副本,该副本可能会再次调用start
,在这种情况下,accept
的所有权将被转移到另一个lambda的成员,而拥有Session
所有权的lambda将再次移至内部boost :: asio存储。在异步调用lambda之后,它将被boost :: asio销毁。但是,在这一点上,所有权已经再次转移。
当start
失败并且拥有Session
的lambda在调用之后被boost :: asio破坏时,Session
对象最终被销毁。
因此,对于与Session
的生命周期有关的未定义行为,我认为这种方法没有问题。如果您使用的是C ++ 17,则可以将if(!errorCode)
放到std::unique_ptr<Session>
参数中。