Подтвердить что ты не робот

Как работать с не подлежащими копированию объектами при вставке в контейнеры в С++

Я ищу наилучшую практику работы с не копируемыми объектами.

У меня есть класс mutex, который, очевидно, не должен быть скопирован. Я добавил конструктор частной копии, чтобы обеспечить его выполнение.

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

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

Любые советы?

4b9b3361

Ответ 1

Три решения здесь:

1. Использовать указатели. Быстрое исправление - сделать его контейнером указателей. a shared_ptr.

Это будет "хорошее" решение, если ваши объекты действительно не подлежат копированию, а использование других контейнеров невозможно.

2. Другие контейнеры. Кроме того, вы можете использовать контейнеры без копирования (которые используют in-place-construction), однако они не очень распространены и в значительной степени несовместимы с STL. (Я пробовал какое-то время, но это просто не хорошо)

Это было бы "божественное" решение, если ваши объекты действительно не подлежат копированию, а использование указателей невозможно.

[edit] С С++ 13 std::vector разрешает конструкцию inplace (emplace_back) и может использоваться для не подлежащих копированию объектов, которые реализуют семантику перемещения. [/Править]

3. Исправьте свою способность к копированию. Если ваш класс можно скопировать как таковой, а мьютекс - нет, вам просто нужно исправить конструктор копирования и оператор присваивания.

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

template <typename TNonCopyable>
struct NeverCopy : public T 
{
    NeverCopy() {}
    NeverCopy(T const & rhs) {}

    NeverCopy<T> & operator=(T const & rhs) { return *this; }
}

И изменив ваш член мьютекса на

NeverCopy<Mutex> m_mutex;

К сожалению, используя этот шаблон, вы теряете специальные конструкторы Mutex.

Предупреждение: "Фиксация" Copy CTor/asignment часто требует блокировки правой стороны в конструкции копирования и блокировки обеих сторон при назначении. К сожалению, невозможно переопределить копию ctor/assign и вызвать реализацию по умолчанию, поэтому трюк NeverCopy может не работать для вас без внешней блокировки. (Есть некоторые другие способы обхода с их собственными ограничениями.)

Ответ 2

Если объект не скопирован, то обычно есть веская причина. И если есть веская причина, вы не должны подорвать это, помещая его в контейнер, который пытается его скопировать.

Ответ 3

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

чтобы привести примеры: reference wrapper (aka boost:: ref, указатель под капотом)

#include <vector>
#include <tr1/functional>
struct Noncopy {
private:
        Noncopy(const Noncopy&) {}
public:
        Noncopy() {}
};
int main()
{
        std::vector<std::tr1::reference_wrapper<Noncopy> > v;
        Noncopy m;
        v.push_back(std::tr1::reference_wrapper<Noncopy>(m));
}

С++ 0x, проверенный с помощью gcc:

#include <vector>
struct Movable {
private:
        Movable(const Movable&) = delete;
public:
        Movable() {}
        Movable(Movable&&) {}
};
int main()
{
        std::vector<Movable> v;
        Movable m;
        v.emplace_back(std::move(m));
}

EDIT: Nevermind, С++ 0x FCD говорит, под 30.4.1/3,

Тип Mutex не может быть скопирован или перемещен.

Итак, вам лучше с указателями на них. Умный или иначе завернутый по мере необходимости.

Ответ 4

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

Ответ 5

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

Некоторые говорили о вещах, которые могут быть перемещаемыми и использовать С++ 0x, в которых контейнеры часто требуют, чтобы их элементы были подвижными, но не требуют их копирования. Я думаю, что это плохое решение, потому что я подозреваю, что мьютексы не должны перемещаться во время их хранения, и это делает невозможным их перемещение.

Итак, единственный реальный оставшийся ответ - указать на мьютексы. Используйте ::std::tr1::shared_ptr#include <tr1/memory>) или ::boost::shared_ptr, чтобы указать на мьютексы. Это требует изменения определений классов, в которых есть мьютексы, но похоже, что вы все равно это делаете.

Ответ 6

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

Но, поскольку вы не указали указателей, есть еще один вариант. Похоже, что мьютексы "иногда могут быть скопированы", возможно, вам следует написать конструктор копирования и оператор присваивания, и они могут вызвать исключение, если мьютекс когда-либо копируется после его инициализации. С другой стороны, нет способа узнать, что вы делаете это неправильно до времени выполнения.

Ответ 7

Используйте интеллектуальные указатели, такие как boost:: shared_ptr или используйте другие контейнеры, например boost:: intrusive. Оба потребуют изменить ваш код, используя.

Ответ 8

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

C::C (C const & c)
// No ctor-initializer here.
{
  MutexLock guard (c.mutex);

  // Do the copy-construction here.
  x = c.x;
}

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

Ответ 9

Используя С++ 11 на Ubuntu 14.04 (который включает emplace_back), я получил это, чтобы работать.

Я обнаружил, что emplace_back работал нормально, но удаление (и, возможно, вставка) не сработало, потому что, когда вектор перетасовывал элементы, чтобы заполнить пробел, он использует:

 *previous = *current;

Я нашел, что трюк позволял назначать перенос в моем классе ресурсов:

  Watch& operator=(Watch &&other);

Это мой класс inotify_watch, который может жить в std::vector:

class Watch {
private:
  int inotify_handle = 0;
  int handle = -1;
  // Erases all knowledge of our resources
  void erase() {
    inotify_handle = 0;
    handle = -1;
  }
public:
  Watch(int inotify_handle, const char *path, uint32_t mask)
      : inotify_handle(inotify_handle),
        handle(inotify_add_watch(inotify_handle, path, mask)) {
    if (handle == -1)
      throw std::system_error(errno, std::system_category());
  }
  Watch(const Watch& other) = delete; // Can't copy it, it a real resource
  // Move is fine
  Watch(Watch &&other)
      : inotify_handle(other.inotify_handle), handle(other.handle) {
    other.erase(); // Make the other one forget about our resources, so that
                   // when the destructor is called, it won't try to free them,
                   // as we own them now
  } 
  // Move assignment is fine
  Watch &operator=(Watch &&other) {
    inotify_handle = other.inotify_handle;
    handle = other.handle;
    other.erase(); // Make the other one forget about our resources, so that
                   // when the destructor is called, it won't try to free them,
                   // as we own them now
    return *this;
  }
  bool operator ==(const Watch& other) {
    return (inotify_handle == other.inotify_handle) && (handle == other.handle);
  }
  ~Watch() {
    if (handle != -1) {
      int result = inotify_rm_watch(inotify_handle, handle);
      if (result == -1)
        throw std::system_error(errno, std::system_category());
    }
  }
};