gpt4 book ai didi

c++ - Unique_ptr 将所有权移动到包含对象的方法

转载 作者:行者123 更新时间:2023-12-02 15:34:39 28 4
gpt4 key购买 nike

我想将 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 被异步调用。 (提升::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

最佳答案

对于您的第一个代码块:
std::unique_ptr<Foo>&& self是一个引用并为其分配一个参数 std::move(foo_p) ,其中 foo_p是一个名为 std::unique_ptr<Foo>只会绑定(bind)引用 selffoo_p , 意思是 self将引用foo_p在调用范围内。

它不会创建任何新的 std::unique_ptr<Foo>托管的所有权Foo对象可以转让。没有移动构造或分配发生,Foo对象仍然随着 foo_p 的破坏而被破坏在调用范围内。

因此,此函数调用本身不存在未定义行为的风险,尽管您可以使用引用 self以可能导致 body 出现未定义行为的方式。

也许你打算拥有 self成为 std::unique_ptr<Foo>而不是 std::unique_ptr<Foo>&& .在那种情况下 self将不是引用,而是托管 Foo 的所有权的实际对象。如果使用 std::move(p_foo) 调用,将通过移动结构传输并且在 foo_p->method(std::move(foo_p)) 中的函数调用后会被销毁连同托管Foo .

此替代变体本身是否具有潜在的未定义行为取决于使用的 C++ 标准版本。

在 C++17 之前,允许编译器在评估 foo_p->method 之前选择评估调用的参数(以及参数的相关移动构造)。 .这意味着,foo_p可能已经从 foo_p->method 开始被评估,导致未定义的行为。这可以按照您建议的方式进行修复。

由于 C++17 保证后缀表达式(这里是 foo_p->method )在调用的任何参数之前被评估,因此调用本身不会有问题。 (但 body 可能会导致其他问题。)

后一种情况的详细信息:
foo_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可以从之前搬走 this作为参数将被初始化。和 B) 被时间感动method运行并使用初始化的 this .

但是 A) 不是问题,因为第 8.2.2:4 节,正如预期的那样:

If the function is a non-static member function, the this parameter of the function shall be initialized with a pointer to the object of the call,



(我们知道这个对象在任何参数被评估之前就被解析了。)

B) 没关系:( another question )

the C++11 specification guarantees that transferring ownership of an object from one unique_ptr to another unique_ptr does not change the location of the object itself



对于您的第二个块:
self(std::move(self))创建类型为 std::unique_ptr<Session> 的 lambda 捕获(不是引用)用引用 self 初始化, 指的是 session_ptraccept 中的 lambda 中.通过 Session 的移动 build 所有权对象从 session_ptr 转移给 lambda 的成员。

然后将 lambda 传递给 async_read_some , 这将(因为 lambda 不是作为非常量左值引用传递)将 lambda 移动到内部存储中,以便以后可以异步调用它。有了这个举动, Session的所有权对象也传输到 boost::asio 内部。
async_read_some立即返回 start 的所有局部变量和 accept 中的 lambda被摧毁。然而 Session的所有权已经转移,因此这里没有由于生命周期问题而未定义的行为。

将异步调用 lambda 的拷贝,这可能会再次调用 start ,在这种情况下 Session 的所有权将被转移到另一个 lambda 的成员和带有 Session 的 lambda所有权将再次转移到内部 boost::asio 存储。 lambda 异步调用后,会被 boost::asio 销毁。然而,在这一点上,所有权已经转移。
Session对象最终被销毁,当 if(!errorCode)失败,并且拥有 std::unique_ptr<Session> 的 lambda在调用后被 boost::asio 销毁。

因此,对于与 Session 相关的未定义行为,我认为这种方法没有问题。的一生。如果您使用的是 C++17,那么删除 && 也很好。在 std::unique_ptr<Session>&& self范围。

关于c++ - Unique_ptr 将所有权移动到包含对象的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58051387/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com