在同一语句中使用并移动unique_ptr是否合法?为何部分场景崩溃?
问题解答
1. 同语句使用并移动unique_ptr的合法性与求值顺序
- 这种写法的合法性完全依赖C版本和参数求值顺序,**C17之前属于未定义行为**,即使是C++17及以上版本也存在风险。
- C++的求值顺序规则:
- 对于成员函数调用,所有C++版本都保证成员函数的
对象表达式(也就是self->socket、self->t这部分)的求值,一定早于所有函数参数的求值。 - 但同一个函数的多个参数之间的求值顺序,在所有C++版本中都是未指定的,编译器可以按任意顺序求值多个参数。
- 对于成员函数调用,所有C++版本都保证成员函数的
- 你崩溃的代码中,
async_send_to有3个参数:self->frame->get_asio_buffer()self->client_endpointstd::bind(...)内部执行std::move(self)转移所有权
如果编译器先求值第三个参数,把self的所有权转移走,self变为空,再去求值前两个参数时解引用空的unique_ptr,就会触发段错误,完全符合你遇到的崩溃现象。
2. 栈追踪的判断正确性
你的判断是正确的。错误提示访问零页偏移0x90的地址,就是对空的unique_ptr执行operator->访问成员时,成员偏移量为0x90,触发了非法内存访问。shared_ptr_base.h的报错只是解引用空指针后,最终访问frame成员(shared_ptr类型)的get方法时触发的崩溃,根因还是self已经为空。
3. 样例代码正常运行的原因
你的样例中async_wait只有1个参数,参数求值顺序不存在歧义:
- 首先求值成员函数的对象表达式
self->t,这一步会先解引用self获取timer对象,此时还没有执行move操作,完全安全。 - 再求值唯一的参数,也就是
std::bind内部的std::move(self),此时所有对self的访问已经完成,move不会影响之前的操作。
所以样例代码不管什么编译器版本,执行顺序都是固定安全的,不会触发崩溃。
修复方案
最简单的修复方式是先把需要用到的成员变量提前取出来,再执行move操作,完全规避求值顺序问题:
auto buffer = self->frame->get_asio_buffer(); auto endpoint = self->client_endpoint; self->socket.async_send_to(buffer, endpoint, std::bind(&download_server::receiver, std::move(self), std::placeholders::_1, std::placeholders::_2));
如果仅面向C17及以上标准编译,也可以直接依赖C17的求值规则,但提前取值的写法兼容性更好,也更清晰。
内容的提问来源于stack exchange,提问作者chandola
相关产品推荐
相关产品推荐

