c++ - 如何通过 memcpy 将可 move 类型推送到/从遗留的基于 C 的容器中弹出?

标签 c++ c++14 move-semantics unique-ptr freertos

我的几个对象包含 unique_ptr 实例作为成员并指定一个 move 构造函数:

struct Foo {
    Foo(Foo&& other): someA(std::move(other.someA)), someB(other.someB){}
    unique_ptr<A> someA;
    B someB;
}

这意味着我可以 move Foo 对象,但不能复制它们。每当它们超出范围时,默认析构函数将调用 unique_ptr 实例的析构函数:

    void someFunc(Foo&& foo);

{
    Foo a;
    Foo b;
    someFunc(Foo()); // compiles
    someFunc(std::move(a)); // compiles -> a.someA is now nullptr
    someFunc(b); // does not compile, because copy is not allowed
} // <- b is destructed and object guarded by b.someA is deleted

现在,我想将这样一个对象 move 到一个容器中,稍后再获取它。这对于 C++ 容器来说不是问题,因为它们可以处理 move 语义。不幸的是,我的容器是由基于 C 的实时操作系统(FreeRTOS 队列:https://www.freertos.org/xQueueSendToBack.html)提供的。它的访问函数 xQueueSendToBackxQueueReceive 获取指向该项目的 void* 以便使用 memcpy 或等效于将元素移入和移出容器。

我的(简化的)队列包装器解决方案如下所示:

template <typename T, std::size_t N>
struct FreeRtosQueue {

    bool sendToBack(T&& item) {
        if (xQueueSendToBack(this->frtosQueueHandle, &item, portMAX_DELAY) != pdTRUE) {
            return false;
        }
        return true;
    } // <- item destructor called without prior move

    T receive() {
        T item;
        xQueueReceive(this->frtosQueueHandle, &item, portMAX_DELAY);
        return item;
    }

    QueueHandle_t frtosQueueHandle;
};

不幸的是,在 sendToBack 结束时,项目的析构函数将被调用,而不会 move 它(从 C++ 的角度来看)。这会导致项目中由 unique_ptr 保护的所有对象被删除。

  1. 如何防止队列中的可 move 对象被破坏?
  2. 您是否发现receive 实现有任何问题?

最佳答案

将 C++ 对象的引用传递给 C 代码时,通常最好将不透明指针传递给动态分配的对象。这通常由 new 创建。在这种情况下,您可以将 newT 的 move 构造函数一起使用,以获取指向动态分配对象的指针,该对象不属于任何 C++ RAII 对象。然后您可以将此指针本身的拷贝存储在 FreeRTOS 队列中。

bool sendToBack(T &&item) {
    T *item_ptr = new T(std::move(item));
    if (xQueueSendToBack(this->frtosQueueHandle, &item_ptr, portMAX_DELAY) != pdTRUE) {
        delete item_ptr;
        return false;
    }
    return true;
}

请注意,item_ptr 的地址被采用,而不是 item_ptr 本身,作为 xQueueSendToBack()pvItemToQueue 参数是指向要复制到队列中的数据的指针,指针本身的原始字节是应该存储在队列中的内容。这也意味着在创建队列时,您应该在调用 uxQueueCreate() 时将 sizeof(T *) 作为 uxItemSize 传递。

我建议更改 sendToBack 以通过切换到转发引用来支持复制和 move ,如下所示:

template<typename ItemT>
bool sendToBack(ItemT &&item) {
    T *item_ptr = new T(std::forward<ItemT>(item));
    if (xQueueSendToBack(this->frtosQueueHandle, &item_ptr, portMAX_DELAY) != pdTRUE) {
        delete item_ptr;
        return false;
    }
    return true;
}

最后,要从队列中接收一个项目,可以按如下方式完成:

T receive() {
    T *item_ptr;
    xQueueReceive(this->frtosQueueHandle, &item_ptr, portMAX_DELAY);
    // Copy / move the item out of the dynamically allocated object and
    // into a local object.
    T item(std::move(*item_ptr));
    // The original object is moved-from but still must be deleted.
    delete item_ptr;
    return item;
}

如果 T 没有 noexcept move/复制构造函数,可以通过立即转移所有权 unique_ptr 如下:

T receive() {
    T *item_ptr;
    xQueueReceive(this->frtosQueueHandle, &item_ptr, portMAX_DELAY);
    std::unique_ptr<T> item_unique_ptr(item_ptr);
    T item(std::move(*item_ptr));
    return item;
}

您手动遍历队列中的项目并删除 FreeRtosQueue 的析构函数中的任何剩余项目也非常重要,因为存储的指向动态分配内存的指针是不受管理的。您还应该禁用该类的复制构造函数(并可选择实现 move 构造函数)。

关于c++ - 如何通过 memcpy 将可 move 类型推送到/从遗留的基于 C 的容器中弹出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52536159/

相关文章:

c++ - 如何使用 AST 进行自定义前端操作和 clang 静态分析

c++ - 配饰设计

c++ - 存在使用命名空间时的全局范围解析

c++ - 强制编译器选择以 const T& 作为参数的复制构造函数

c++ - std::vector 不使用具有 noexcept move 构造的对象调用 move 构造函数

c++ - 我的调试器说我有一个段错误但不能告诉我在哪里,发生在非常特殊的情况下

c++ - 当不涉及复制/移动时,在 C++11/14 中按值返回局部变量会导致返回值由右值构造吗?

c++ - 计算 std::vector 的复制和 move 次数

c++ - 运算符可以识别右值吗?

c++ - 如何让 tr1::array 分配对齐内存?