引言
c++11前没有右值引用,我们怎么将一个包含资源的临时对象和“将亡值”转移或者交换到自己的变量中呢?我以前的做法是bitwise swap
,即将两个对象的内存按位交换。这种做法一直工作得很好,直到最近一段代码在Linux上运行很OK换到windows上就崩溃,我才开始思考这种做法的安全性。
出事代码
公司代码有个线程调度框架,接口像极了WIN32程序窗口回调函数,通过一个整形MSGID来投递和区分消息处理
class TaskModule
{
public:
virtual ~TaskModule() = default;
virtual bool post_task(uint32_t msgid, uint64_t param, const void *data, size_t len) = 0;
};
其中,post_task
的data
和len
参数用于传递大块数据,TaskModule
实现类内部创建一块连续内存的任务节点TaskNode
,然后将data
数据复制到TaskNode
后面,代码大致如下:
struct TaskNode
{
uint32_t msgid = 0;
uint64_t param = 0;
size_t len = 0;
void *data() { return const_cast<void *>(this + 1); }
const void *data() const { return const_cast<void *>(this + 1); }
};
class TaskModuleImpl : public TaskModule
{
public:
bool post_task(uint32_t msgid, uint64_t param, const void *data, size_t len)
{
TaskNode *task = reinterpret_cast<TaskNode *>(malloc(sizeof(TaskNode) + len)));
assert(task);
task->msgid = msgid;
task->param = param;
task->len = len;
memcpy(task->data(), data, len);
return post_task(task);
}
private:
bool post_task(TaskNode *task);
};
为了节省一次内存分配,我使用了replacement new
在外面的临时内存上构建对象,然后将临时内存传给post_task
的data
,在task执行函数中调用构建对象的析构函数。
template<typename T>
bool post_task(TaskModule *tm, uint32_t msgid, T&& obj)
{
char buf[sizeof(T)];
return tm->post_task(msgid, 0, new (buf) T(std::forward<T>(obj)), sizeof(obj));
}
void do_task(uint32_t msgid, void *data, size_t len)
{
auto task = reinterpret_cast<MyTask *>(data);
// task->xxxx
task->~MyTask();
}
这种方式一直工作的很好,但在VC++上一旦MyTask
使用了std::string
和std::function
,程序就会崩溃,do_task
里的程序内存已经不可用。
查找原因
这当然和vc++ std::string
和std::function
的实现有关。研究源码之后,发现是std::string
和std::function
小内存优化或者SSO
使对象的内部指针成员指向了一块内部内存,memcpy
以后的对象指针还是指向原对象内存,原对象为栈上变量,离开作用域之后内存不再可用,使用新对象必然就崩溃了。