Невозможно скомпилировать вектор & lt; queue & lt; t & lt; T1, T2 & gt; & gt; & gt; & gt; [Дубликат]

Другое событие NullPointerException возникает, когда объявляется массив объектов, а затем сразу же пытается разыменовать его внутри.

String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

Этот конкретный NPE можно избежать, если порядок сравнения отменяется ; а именно, использовать .equals для гарантированного непустого объекта.

Все элементы внутри массива инициализируются их общим начальным значением ; для любого типа массива объектов, это означает, что все элементы null.

Вы должны инициализировать элементы в массиве перед доступом или разыменованием их.

String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

64
задан perror 1 May 2015 в 11:42
поделиться

5 ответов

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

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_)
    {}
83
ответ дан Howard Hinnant 19 August 2018 в 17:30
поделиться
  • 1
    Ваш конструктор копирования назначает поля, он не копирует их. Это означает, что они должны быть построены по умолчанию, что является неудачным ограничением. – Yakk - Adam Nevraumont 1 May 2015 в 18:25
  • 2
    @Yakk: Да, включение mutexes в типы классов не является «одним истинным способом». Это инструмент в панели инструментов, и если вы хотите его использовать, вот как. – Howard Hinnant 1 May 2015 в 19:02
  • 3
    @Yakk: Найдите мой ответ для строки & quot; C ++ 14 & quot ;. – Howard Hinnant 1 May 2015 в 19:02
  • 4
    ах, извините, я пропустил этот C ++ 14 бит. – Yakk - Adam Nevraumont 1 May 2015 в 19:41

Прежде всего, в вашем дизайне должно быть что-то не так, если вы хотите переместить объект, содержащий мьютекс.

Но если вы все равно решите это сделать, вам нужно создать новый мьютекс в конструкторе перемещения, например:

// movable
struct B{};

class A {
    B b;
    std::mutex m;
public:
    A(A&& a)
        : b(std::move(a.b))
        // m is default-initialized.
    {
    }
};

Это поточно-безопасное, потому что движение конструктор может с уверенностью предположить, что его аргумент нигде не используется, поэтому блокировка аргумента не требуется.

2
ответ дан Anton Savin 19 August 2018 в 17:30
поделиться
  • 1
    Это не потокобезопасно. Что делать, если a.mutex заблокирован: вы потеряете это состояние. -1 – Dieter Lücking 1 May 2015 в 12:14
  • 2
    @ DieterLücking До тех пор, пока аргумент является единственной ссылкой на перемещенный объект, нет разумной причины блокировки его мьютекса. И даже если это так, нет причин блокировать мьютекс вновь созданного объекта. И если это так, это аргумент в пользу плохой конструкции движимых объектов с мьютексами. – Anton Savin 1 May 2015 в 12:18
  • 3
    @ DieterLücking Это просто неправда. Можете ли вы предоставить код, иллюстрирующий проблему? И не в форме A a; A a2(std::move(a)); do some stuff with a. – Anton Savin 1 May 2015 в 12:25
  • 4
    Однако, если бы это было наилучшим образом, я бы порекомендовал все равно new -вставить экземпляр и поместить его в std::unique_ptr - который кажется более чистым и вряд ли приведет к проблемам путаницы. Хороший вопрос. – Mike Vine 1 May 2015 в 12:32
  • 5
    @MikeVine Я думаю, вы должны добавить его в качестве ответа. – Anton Savin 1 May 2015 в 12:37

Учитывая, там, кажется, не быть хороший, чистый, простой способ ответить на этот - решение Антона I думать является правильным, но его определенно спорно, если лучший ответ не приходит, я бы рекомендовал положить такой класс в куче и ухаживать за ним через std::unique_ptr:

auto a = std::make_unique<A>();

Теперь он полностью перемещаемый, и любой, у кого есть блокировка внутреннего мьютекса, в то время как происходит движение, по-прежнему безопасен, даже если его обсуждать, хорошо ли это делать

. Если вам нужна семантика копирования, просто используйте

auto a2 = std::make_shared<A>();
5
ответ дан Mike Vine 19 August 2018 в 17:30
поделиться

Использование мьютексов и семантики перемещения C ++ - отличный способ безопасно и эффективно передавать данные между потоками.

Представьте себе поток «производителя», который производит партии строк и предоставляет их (одному или нескольким) потребителям , Эти партии могут быть представлены объектом, содержащим (потенциально большие) std::vector<std::string> объекты. Мы абсолютно хотим «переместить» внутреннее состояние этих векторов в своих потребителей без ненужного дублирования.

Вы просто признаете мьютекс как часть объекта, не являющегося частью состояния объекта. То есть, вы не хотите перемещать мьютексы.

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

Если вы только когда-либо переходите от объекта «продюсер» общего состояния к локальному «потребляющему» объекту потока, у вас может быть достаточно, чтобы заблокировать перемещение с объекта.

Если это более общий дизайн, вам нужно будет заблокируйте оба. В этом случае вам нужно подумать о блокировке блокировки.

Если это потенциальная проблема, используйте std::lock() для получения блокировок на обоих мьютексах без блокировки.

http://en.cppreference.com/w/cpp/thread/lock

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

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

Короче, если потенциальный одновременный доступ к перемещенному объекту составляет запись, это, вероятно, будет ОК. Если это будет прочитано, подумайте, почему нормально читать произвольное состояние.

3
ответ дан Persixty 19 August 2018 в 17:30
поделиться

Это перевернутый ответ. Вместо того, чтобы встраивать «эти объекты нужно синхронизировать» в качестве базы этого типа, вместо этого вставляйте под любым типом.

Вы имеете дело с синхронизированным объектом по-разному. Одна из серьезных проблем - вы должны беспокоиться о взаимоблокировках (блокировка нескольких объектов). Это также никогда не должно быть вашей «стандартной версией объекта»: синхронизированные объекты предназначены для объектов, которые будут в конфликте, и ваша цель должна заключаться в том, чтобы свести к минимуму конфликт между потоками, а не подметать его под ковер.

Но синхронизация объектов по-прежнему полезна. Вместо наследования от синхронизатора мы можем написать класс, который обертывает произвольный тип при синхронизации. Пользователи должны перейти через несколько обручей, чтобы выполнять операции над объектом теперь, когда они синхронизированы, но они не ограничены каким-то ограниченным набором операций с ограниченным ручным управлением для объекта. Они могут составлять несколько операций над объектом в один или работать с несколькими объектами.

Вот синхронизированная обертка вокруг произвольного типа T:

template<class T>
struct synchronized {
  template<class F>
  auto read(F&& f) const&->std::result_of_t<F(T const&)> {
    return access(std::forward<F>(f), *this);
  }
  template<class F>
  auto read(F&& f) &&->std::result_of_t<F(T&&)> {
    return access(std::forward<F>(f), std::move(*this));
  }
  template<class F>
  auto write(F&& f)->std::result_of_t<F(T&)> {
    return access(std::forward<F>(f), *this);
  }
  // uses `const` ness of Syncs to determine access:
  template<class F, class... Syncs>
  friend auto access( F&& f, Syncs&&... syncs )->
  std::result_of_t< F(decltype(std::forward<Syncs>(syncs).t)...) >
  {
    return access2( std::index_sequence_for<Syncs...>{}, std::forward<F>(f), std::forward<Syncs>(syncs)... );
  };
  synchronized(synchronized const& o):t(o.read([](T const&o){return o;})){}
  synchronized(synchronized && o):t(std::move(o).read([](T&&o){return std::move(o);})){}  
  // special member functions:
  synchronized( T & o ):t(o) {}
  synchronized( T const& o ):t(o) {}
  synchronized( T && o ):t(std::move(o)) {}
  synchronized( T const&& o ):t(std::move(o)) {}
  synchronized& operator=(T const& o) {
    write([&](T& t){
      t=o;
    });
    return *this;
  }
  synchronized& operator=(T && o) {
    write([&](T& t){
      t=std::move(o);
    });
    return *this;
  }
private:
  template<class X, class S>
  static auto smart_lock(S const& s) {
    return std::shared_lock< std::shared_timed_mutex >(s.m, X{});
  }
  template<class X, class S>
  static auto smart_lock(S& s) {
    return std::unique_lock< std::shared_timed_mutex >(s.m, X{});
  }
  template<class L>
  static void lock(L& lockable) {
      lockable.lock();
  }
  template<class...Ls>
  static void lock(Ls&... lockable) {
      std::lock( lockable... );
  }
  template<size_t...Is, class F, class...Syncs>
  friend auto access2( std::index_sequence<Is...>, F&&f, Syncs&&...syncs)->
  std::result_of_t< F(decltype(std::forward<Syncs>(syncs).t)...) >
  {
    auto locks = std::make_tuple( smart_lock<std::defer_lock_t>(syncs)... );
    lock( std::get<Is>(locks)... );
    return std::forward<F>(f)(std::forward<Syncs>(syncs).t ...);
  }

  mutable std::shared_timed_mutex m;
  T t;
};
template<class T>
synchronized< T > sync( T&& t ) {
  return {std::forward<T>(t)};
}

C ++ 14 и C ++ 1z.

это предполагает, что операции const являются безопасными с несколькими считывателями (что предполагает контейнер std).

Использовать внешний вид например:

synchronized<int> x = 7;
x.read([&](auto&& v){
  std::cout << v << '\n';
});

для int с синхронизированным доступом.

Я бы посоветовал не иметь synchronized(synchronized const&). Это редко бывает необходимо.

Если вам нужно synchronized(synchronized const&), у меня возникнет соблазн заменить T t; на std::aligned_storage, разрешить конструкцию ручного размещения и ручное уничтожение. Это позволяет правильное управление жизненным циклом.

Если это не удалось, мы могли бы скопировать источник T, а затем прочитать из него:

synchronized(synchronized const& o):
  t(o.read(
    [](T const&o){return o;})
  )
{}
synchronized(synchronized && o):
  t(std::move(o).read(
    [](T&&o){return std::move(o);})
  )
{}

для назначения:

synchronized& operator=(synchronized const& o) {
  access([](T& lhs, T const& rhs){
    lhs = rhs;
  }, *this, o);
  return *this;
}
synchronized& operator=(synchronized && o) {
  access([](T& lhs, T&& rhs){
    lhs = std::move(rhs);
  }, *this, std::move(o));
  return *this;
}
friend void swap(synchronized& lhs, synchronized& rhs) {
  access([](T& lhs, T& rhs){
    using std::swap;
    swap(lhs, rhs);
  }, *this, o);
}

размещение и выровненные версии хранилищ немного беспорядочны. В большинстве случаев доступ к t будет заменен на функцию-член T&t() и T const&t()const, за исключением конструкции, где вам придется перепрыгивать через некоторые обручи.

Сделав synchronized вместо обертки части класса, все, что мы должны убедиться, состоит в том, что класс внутренне уважает const как многократный читатель и записывает его однопоточным способом.

В редких случаях нам нужно синхронизированный экземпляр, мы прыгаем через обручи, как показано выше.

Извините за любые опечатки в приведенном выше. Вероятно, есть некоторые.

. Преимущество вышеизложенного заключается в том, что n-арные произвольные операции над объектами synchronized (одного и того же типа) работают вместе, без необходимости жестко кодировать их перед началом работы. Добавить в объявление друга и n-ary synchronized объекты нескольких типов могут работать вместе. Мне может потребоваться переместить access из встроенного друга, чтобы иметь дело с перегрузками в этом случае.

live example

5
ответ дан Yakk - Adam Nevraumont 19 August 2018 в 17:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: