Использование unique_ptr в паре - PullRequest
1 голос
/ 20 февраля 2020

Мой код:

template <typename T>//, T value_if_empty>
class AtomicQueue {
    std::mutex m;
    std::queue<T> q;
    T value_if_empty;

public:
    AtomicQueue(const T& emptyval) : value_if_empty(std::move(emptyval)) {};
    void push(const T& t)
    {
        m.lock();
        q.push(std::move(t));
        m.unlock();
    }
    T pop()
    {
        T a = std::move(value_if_empty);
        m.lock();
        if (!q.empty())
        {
            a = std::move(q.front());
            q.pop();
        }
        m.unlock();
        return a;
    }
};

AtomicQueue<int> A(0);
AtomicQueue<std::unique_ptr<Class1>> B(nullptr);
AtomicQueue < std::pair<int, std::unique_ptr<Class1>>> C({ 0,nullptr });

Этот код компилируется, если я объявляю только A и B, но завершается неудачно, когда C также объявляется. Ошибка: 'std::pair<int,std::unique_ptr<Class1,std::default_delete<Class1>>>::pair(const std::pair<int,std::unique_ptr<Class1,std::default_delete<Class1>>> &)': attempting to reference a deleted function

Кажется, проблема в том, что std::pair<int, std::unique_ptr<Class1>> не имеет конструктора копирования. Но то же самое верно для std::unique_ptr<Class1>. Так почему же декларация для B компилируется, а C - нет? Кроме того, где именно используется конструктор копирования? Извините, если ответ тривиален. Я новичок в использовании unique_ptr и конструкторов перемещения.

Дополнительный вопрос: существует ли более чистый или лучший способ использования value_if_empty? Я не хочу, чтобы во время использования unique_ptr передавалось ничего, кроме nullptr.

Редактировать: B также не компилируется. (как упомянуто в комментарии NathanOliver). Итак, теперь вопрос в том, как мне удалить эту ошибку?

Ответы [ 2 ]

5 голосов
/ 20 февраля 2020

In

AtomicQueue(const T& emptyval) : value_if_empty(std::move(emptyval)) {}

std::move(emptyval) не перемещается, поскольку emptyval является const.

Вам потребуются перегрузки:

AtomicQueue(const T& emptyval) : value_if_empty(emptyval) {}
AtomicQueue(T&& emptyval) : value_if_empty(std::move(emptyval)) {}

или изменение до:

AtomicQueue(T emptyval) : value_if_empty(std::move(emptyval)) {}

Примечание: То же самое относится к push.

4 голосов
/ 20 февраля 2020

Это потому, что вы ничего не перемещаете в своей функции. Они получают const T&, но константы не могут быть перемещены. Вызов std::move на const не будет двигаться и абсолютно ничего не делает.

Видите, движение - это просто приведение к значению. Фактическое перемещение происходит в конструкторе перемещения и назначении перемещения. Такие конструкторы перемещения объявляются так:

unique_ptr(unique_ptr&& other);

Как видите, это неконстантная ссылка. Поскольку вы не можете вызвать конструктор перемещения, он пытается вместо этого скопировать. Это источник ошибки.

Как вы можете исправить это тогда?

Просто добавьте необходимые перегрузки и удалите лишние ходы:

template <typename T>
class AtomicQueue {
    std::mutex m;
    std::queue<T> q;
    T value_if_empty;

public:
    // copy, it's an lvalue
    AtomicQueue(const T& emptyval) : value_if_empty(emptyval) {};

    // move, it's an rvalue
    AtomicQueue(T&& emptyval) : value_if_empty(std::move(emptyval)) {};

    void push(const T& t)
    {
        m.lock();
        q.push(t); // same here
        m.unlock();
    }

    void push(T&& t)
    {
        m.lock();
        q.push(std::move(t)); // same here
        m.unlock();
    }
    T pop()
    {
        T a = std::move(value_if_empty);
        m.lock();
        if (!q.empty())
        {
            a = std::move(q.front());
            q.pop();
        }
        m.unlock();
        return a;
    }
};

Живой пример


Также обратите внимание, что в вашем классе есть фундаментальная ошибка. Посмотрите на эту строку:

T a = std::move(value_if_empty);

Если pop вызывается более одного раза, value_if_empty будет перемещено из значения, и вы вернете его. Ваша функция pop может быть вызвана только один раз перед возвратом неопределенных значений.

Вам нужно будет либо скопировать value_if_empty, создать его по умолчанию или заменить его заводской функцией, которая будет возвращать новое значение в возьмите, если пусто.

Моим любимым решением было бы построить его по умолчанию.

Вот, тем не менее, пример с фабричной функцией:

template <typename T>
class AtomicQueue {
    std::function<T()> make_empty_value;

public:
    T pop()
    {
        T a = make_empty_value();
        // ...
    }
};

Затем передайте его вашему классу при его создании:

AtomicQueue<std::unique_ptr<Class1>> B([]{ return std::unique_ptr<Class1>{nullptr}; });

Если вы хотите избежать накладных расходов на std::function, вы можете заменить переменную-член параметром шаблона типа lambda.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...