Как вы создаете переменную с именем переменной? [Ответил] [дубликат]

Начнем с немного кода:

class A
{
    using MutexType = std::mutex;
    using ReadLock = std::unique_lock<MutexType>;
    using WriteLock = std::unique_lock<MutexType>;

    mutable MutexType mut_;

    std::string field1_;
    std::string field2_;

public:
    ...

Я добавил некоторые довольно наводящие алиасы типов, которые мы действительно не будем использовать в C ++ 11, но будем много более полезен в C ++ 14.

Ваш вопрос сводится к следующему:

Как написать конструктор перемещения и переместить оператор присваивания для этого класса?

Начнем с конструктора перемещения.

Move Constructor

Обратите внимание, что элемент mutex сделан mutable. Строго говоря, это не обязательно для участников движения, но я предполагаю, что вы также хотите копировать участников. Если это не так, нет необходимости делать мьютекс mutable.

При построении A вам не нужно блокировать this->mut_. Но вам нужно заблокировать mut_ объекта, из которого вы строите (переместить или скопировать). Это можно сделать так:

    A(A&& a)
    {
        WriteLock rhs_lk(a.mut_);
        field1_ = std::move(a.field1_);
        field2_ = std::move(a.field2_);
    }

Обратите внимание, что сначала нам нужно было по умолчанию сконструировать элементы this, а затем назначить их значения только после блокировки a.mut_.

Назначение перемещения

Оператор присваивания перемещения значительно сложнее, потому что вы не знаете, обращается ли какой-либо другой поток к lhs или rhs выражения присваивания. И вообще, вам нужно защититься от следующего сценария:

// Thread 1
x = std::move(y);

// Thread 2
y = std::move(x);

Вот оператор назначения перемещения, который правильно защищает описанный выше сценарий:

    A& operator=(A&& a)
    {
        if (this != &a)
        {
            WriteLock lhs_lk(mut_, std::defer_lock);
            WriteLock rhs_lk(a.mut_, std::defer_lock);
            std::lock(lhs_lk, rhs_lk);
            field1_ = std::move(a.field1_);
            field2_ = std::move(a.field2_);
        }
        return *this;
    }

Обратите внимание, что необходимо используйте std::lock(m1, m2) для блокировки двух мьютексов вместо того, чтобы просто запирать их один за другим. Если вы блокируете их один за другим, тогда, когда два потока назначают два объекта в противоположном порядке, как показано выше, вы можете получить тупик. Точка std::lock заключается в том, чтобы избежать этого тупика.

Копировать конструктор

Вы не спрашивали о членах копии, но мы могли бы также поговорить о них сейчас (если не вам, кому-то понадобятся).

    A(const A& a)
    {
        ReadLock  rhs_lk(a.mut_);
        field1_ = a.field1_;
        field2_ = a.field2_;
    }

Конструктор копирования выглядит так же, как конструктор перемещения, за исключением псевдонима ReadLock, вместо WriteLock. В настоящее время эти оба псевдонима std::unique_lock<std::mutex>, и поэтому на самом деле это не имеет никакого значения.

Но в C ++ 14 у вас будет возможность сказать следующее:

    using MutexType = std::shared_timed_mutex;
    using ReadLock  = std::shared_lock<MutexType>;
    using WriteLock = std::unique_lock<MutexType>;

Это может быть оптимизацией, но не определенно. Вы должны будете измерить, чтобы определить, есть ли это. Но с этим изменением можно копировать конструкцию из одних и тех же строк в нескольких потоках одновременно. Решение C ++ 11 заставляет вас делать такие потоки последовательными, даже если rhs не изменяется.

Назначение копирования

Для полноты здесь оператор присваивания копий, который должен быть достаточно понятным после прочтения обо всем остальном:

    A& operator=(const A& a)
    {
        if (this != &a)
        {
            WriteLock lhs_lk(mut_, std::defer_lock);
            ReadLock  rhs_lk(a.mut_, std::defer_lock);
            std::lock(lhs_lk, rhs_lk);
            field1_ = a.field1_;
            field2_ = a.field2_;
        }
        return *this;
    }

И т. д.

Любые другие члены или свободные функции, которые получают доступ к состоянию A, также должны будут быть защищенным, если вы ожидаете, что несколько потоков смогут сразу вызвать их. Например, здесь swap:

    friend void swap(A& x, A& y)
    {
        if (&x != &y)
        {
            WriteLock lhs_lk(x.mut_, std::defer_lock);
            WriteLock rhs_lk(y.mut_, std::defer_lock);
            std::lock(lhs_lk, rhs_lk);
            using std::swap;
            swap(x.field1_, y.field1_);
            swap(x.field2_, y.field2_);
        }
    }

Обратите внимание, что если вы просто зависите от std::swap, выполняющего задание, блокировка будет неточной детализации, блокировки и разблокировки между тремя ходами, которые std::swap будет выполнять внутренне.

Действительно, размышление о swap может дать вам представление об API, который может потребоваться для обеспечения «потокобезопасности» A, который в целом будет отличаться от «небезобезопасный» API из-за проблемы «блокировки гранулярности».

Также обратите внимание на необходимость защиты от «самообмена». «self-swap» должен быть не-op. Без самопроверки рекурсивно блокирует один и тот же мьютекс. Это также можно было бы решить без самопроверки с помощью std::recursive_mutex для MutexType.

Обновить

В комментариях ниже Якк довольно недоволен необходимостью по умолчанию строить вещи в копировать и перемещать конструкторы (и у него есть точка). Если вы достаточно решительно относитесь к этой проблеме, так что вы готовы потратить на нее память, вы можете избежать ее так:

  • Добавьте все типы блокировок, которые вам нужны в качестве элементов данных. Эти члены должны находиться перед защищаемыми данными:
    mutable MutexType mut_;
    ReadLock  read_lock_;
    WriteLock write_lock_;
    // ... other data members ...
    
  • И затем в конструкторах (например, конструктор копирования) выполните следующее:
    A(const A& a)
        : read_lock_(a.mut_)
        , field1_(a.field1_)
        , field2_(a.field2_)
    {
        read_lock_.unlock();
    }
    

К сожалению, Yakk удалил свой комментарий, прежде чем у меня была возможность завершить это обновление.

Обновление 2

И dyp придумал это хорошее предложение:

    A(const A& a)
        : A(a, ReadLock(a.mut_))
    {}
private:
    A(const A& a, ReadLock rhs_lk)
        : field1_(a.field1_)
        , field2_(a.field2_)
    {}
0
задан The Anonymous Hacker 23 January 2019 в 07:08
поделиться