Как работать с некопируемыми объектами при вставке в контейнеры в C ++ - PullRequest
12 голосов
/ 11 августа 2010

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

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

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

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

Любой совет?

Ответы [ 9 ]

11 голосов
/ 11 августа 2010

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

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

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

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

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

[править] В C ++ 13 std :: vector позволяет создавать на месте (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.

[править] Предупреждение: «Исправление» копирования CTor / присваивания часто требует, чтобы вы блокировали правую сторону на конструкции копирования и блокировали обе стороны при назначении. К сожалению, нет способа переопределить копию ctor / assignment и , вызывающие реализацию по умолчанию, поэтому трюк NeverCopy может не работать без внешней блокировки. (Существуют и другие обходные пути со своими ограничениями.)

4 голосов
/ 11 августа 2010

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

, чтобы привести примеры: ссылочная оболочка (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));
}

C ++ 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));
}

РЕДАКТИРОВАТЬ: Nevermind, C ++ 0x FCD говорит, под 30.4.1 / 3,

Тип Mutex не должен быть ни копируемым, ни подвижным.

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

4 голосов
/ 11 августа 2010

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

2 голосов
/ 11 августа 2010

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

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

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

2 голосов
/ 11 августа 2010

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

1 голос
/ 11 августа 2010

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

1 голос
/ 11 августа 2010

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

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

0 голосов
/ 21 декабря 2014

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

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

 *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's 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());
    }
  }
};
0 голосов
/ 11 августа 2010

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

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

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

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

...