Как переместить / выдвинуть подвижный тип в / из старого контейнера на основе C через memcpy? - PullRequest
0 голосов
/ 27 сентября 2018

Некоторые из моих объектов содержат unique_ptr экземпляров в качестве членов и задают конструктор перемещения:

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

Это означает, что я могу перемещать 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

Теперь я хочу переместить такой объект в контейнер и позже получить его снова.Это не проблема для контейнеров C ++, потому что они могут обрабатывать семантику перемещения.К сожалению, мой контейнер предоставляется ОС реального времени на основе Си (Очередь FreeRTOS: https://www.freertos.org/xQueueSendToBack.html). Его функции доступа xQueueSendToBack и xQueueReceive позволяют получить 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будет вызываться деструктор элемента без его перемещения (с точки зрения C ++), в результате чего все объекты, охраняемые unique_ptr s внутри элемента, будут удалены.

  1. Как я могу предотвратитьуничтожение подвижных объектов в очереди?
  2. Видите ли вы какие-либо проблемы с реализацией receive?

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

При передаче ссылок на объекты C ++ в код на C обычно лучше передавать непрозрачный указатель на динамически размещаемый объект.Это, как правило, создается new.В этом случае вы можете использовать new с конструктором перемещения T, чтобы получить указатель на динамически размещенный объект, не принадлежащий ни одному объекту 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() 's * 1010Аргумент * - это указатель на данные для копирования в очередь, а необработанные байты самого указателя - это то, что должно храниться в очереди.Это также означает, что при создании очереди вы должны передать sizeof(T *) как uxItemSize при вызове uxQueueCreate().

Я бы порекомендовал изменить sendToBack, чтобы также поддерживать копирование и переход к переключению напереадресация ссылки следующим образом:

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 конструктора перемещения / копирования путем немедленной передачи права собственности 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, так как в противном случае сохраненные указатели на динамически распределенную память неуправляемы.Вам также следует отключить конструктор копирования класса (и при необходимости реализовать конструктор перемещения).

0 голосов
/ 27 сентября 2018

Проблема в том, что перемещение через memcpy определено только для тривиально подвижных классов, и вы пытаетесь использовать его в классе, содержащем std::unique_ptr.

Но можно извлечь базовый указательиз уникального указателя через release, а затем используйте этот указатель для сброса его в новый уникальный указатель через reset.

Таким образом, предполагая, что B легко копируется / перемещается, вы можете использовать вспомогательный класс

struct CFoo {
    A* someA;         // this is a trivial type
    B someB;          // valid provided B is trivially movable
};

Затем вы можете построить очередь FreeRTOS из CFoo

template

struct FreeRtosQueue {

    bool sendToBack(Foo&& item) {
        CFoo cf { item.someA.get(), std::move(item.someB) };
        if (xQueueSendToBack(this->frtosQueueHandle, &cf, portMAX_DELAY) != pdTRUE) {
            return false;
        }
        return true;
    } // <- no destructor called here because somaA and someB have been "moved"

    Foo receive() {
        Foo item;
        CFoo cf;
        xQueueReceive(this->frtosQueueHandle, &cf, portMAX_DELAY);
        item.someA.reset(cf.someA);       // Ok, someA owns back the A object
        item.someB = std::move(cf.someB)
        return item;
    }

    QueueHandle_t frtosQueueHandle;
};
...