Ada*_*dam 5 c++ shared-ptr boost-asio unique-ptr
我想将 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));
Run Code Online (Sandbox Code Playgroud)
这可以编译,但我不知道这是否不是未定义的行为。因为我在调用对象的方法时也离开了该对象。
是UB吗?
如果是的话,我可能可以用以下方法修复它:
auto raw_foo_p = foo_p.get();
raw_foo_p->method(std::move(foo_p))
Run Code Online (Sandbox Code Playgroud)
正确的?
(可选动机:)
传递对象以延长其生命周期。它会存在于 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;
}
Run Code Online (Sandbox Code Playgroud)
编译为:
g++ main.cpp -std=c++17 -lpthread -lboost_system
对于您的第一个代码块:
\n\nstd::unique_ptr<Foo>&& self是一个引用并为其分配一个参数std::move(foo_p),其中foo_p是一个命名std::unique_ptr<Foo>只会将引用绑定self到foo_p,这意味着将在调用范围中self引用。foo_p
它不会创建任何新的std::unique_ptr<Foo>被管理对象的所有权Foo可以转移的对象。不会发生移动构造或赋值,并且对象仍然会随着调用范围内Foo的销毁而被销毁。foo_p
因此,此函数调用本身不存在未定义行为的风险,尽管您可能以可能self导致主体中出现未定义行为的方式使用引用。
也许您打算成为selfastd::unique_ptr<Foo>而不是std::unique_ptr<Foo>&&。在这种情况下,self它不是一个引用,而是一个实际对象,Foo如果调用 with ,则托管的所有权将通过移动构造转移到该实际对象,并且在函数与托管一起std::move(p_foo)调用后将被销毁。foo_p->method(std::move(foo_p))Foo
此替代变体本身是否具有潜在的未定义行为取决于所使用的 C++ 标准版本。
\n\n在 C++17 之前,允许编译器选择在评估 之前评估调用的参数(以及参数的关联移动构造)foo_p->method。这意味着,它foo_p可能已经从foo_p->method评估时移出,导致未定义的行为。可以按照与您建议的方式类似的方式来修复此问题。
从 C++17 开始,可以保证后缀表达式(此处foo_p->method)在调用的任何参数之前计算,因此调用本身不会出现问题。(身体仍然可能导致其他问题。)
后一种情况的详细信息:
\n\nfoo_p->method被解释为(foo_p->operator->())->method,因为std::unique_ptr提供了这个operator->()。(foo_p->operator->())将解析为指向Foo由 管理的对象的指针std::unique_ptr。最后一个->method解析为该对象的成员函数method。在 C++17 中,此评估发生在对任何参数进行评估之前method,因此是有效的,因为尚未foo_p发生任何移动。
那么参数的评估顺序在设计上是未指定的。所以可能A) unique_ptr可以从之前的参数被初始化时foo_p移走。thisB )它将在运行时移出并使用初始化的.methodthis
但A)不是问题,因为 \xc2\xa7 8.2.2:4,正如预期的那样:
\n\n\n\n\n如果函数是非静态成员函数,则函数的this参数应使用指向调用对象的指针进行初始化,
\n
(我们知道这个对象在任何参数被求值之前就已经被解析了。)
\n\nB)并不重要,因为:(另一个问题)
\n\n\n\n\nC++11 规范保证将对象的所有权从一个 unique_ptr 转移到另一个 unique_ptr 不会更改对象本身的位置
\n
对于你的第二个块:
\n\nself(std::move(self))创建一个std::unique_ptr<Session>用引用初始化的类型(不是引用)的 lambda 捕获self,该引用session_ptr在 lambda in 中引用accept。通过移动构造,对象的所有权从lambda 的成员Session转移。session_ptr
然后将 lambda 传递给async_read_some,这将(因为 lambda 不是作为非常量左值引用传递)将 lambda 移动到内部存储中,以便稍后可以异步调用它。通过此移动,对象的所有权Session也转移到 boost::asio 内部。
async_read_some立即返回,因此 的所有局部变量start和 lambda 都accept被销毁。然而,所有权Session已经转移,因此这里不存在由于生命周期问题而导致的未定义行为。
将异步调用 lambda 的副本,这可能会再次调用start,在这种情况下, 的所有权Session将转移到另一个 lambda 的成员,并且具有所有权的 lambdaSession将再次移动到内部 boost::asio 存储。异步调用 lambda 后,它将被 boost::asio 销毁。然而此时,所有权已经再次转移。
该Session对象最终被销毁,当if(!errorCode)并且拥有该对象的 lambdastd::unique_ptr<Session>在调用后被 boost::asio 销毁。
因此,对于与Session\ 的生命周期相关的未定义行为,我认为这种方法没有问题。如果您使用的是 C++17 那么也可以&&将std::unique_ptr<Session>&& self。
| 归档时间: |
|
| 查看次数: |
1170 次 |
| 最近记录: |