在C ++中插入容器时如何处理不可复制的对象

时间:2010-08-11 10:44:28

标签: c++ noncopyable

我正在寻找处理不可复制对象的最佳做法。

我有一个互斥类,显然不应该是可复制的。 我添加了一个私有拷贝构造函数来强制执行。

这破坏了代码 - 有些地方只需要修复,但我有一个普遍的问题 将使用互斥锁作为数据成员或继承的类插入到容器中。

这通常发生在容器初始化期间,因此互斥锁尚未初始化,因此没问题,但没有复制构造函数它不起作用。更改容器以包含指针是不可接受的。

有任何建议吗?

9 个答案:

答案 0 :(得分:11)

这里有三个解决方案:

<强> 1。使用指针 - 快速解决方法是使其成为指针的容器 - 例如shared_ptr

如果您的对象真的是不可复制的,那将是“好”的解决方案,并且无法使用其他容器。

<强> 2。其他容器 - 或者,您可以使用非复制容器(使用就地构造),但它们不常见且与STL大部分不兼容。 (我已经尝试了一段时间,但这根本不好)

如果您的对象真的是不可复制的,那么这将是“上帝”解决方案,并且无法使用指针。

[编辑]使用C ++ 13,std :: vector允许inplace构造(emplace_back),并且可以用于实现移动语义的不可复制对象。 [/编辑]

第3。修复可复制性 - 如果您的类是可复制的,并且互斥锁不是,则“只需”修复复制构造函数和赋值运算符。

写它们很痛苦,因为你通常需要复制&amp;指定除互斥锁之外的所有成员,但通常可以通过以下方式简化:

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

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

将mutex成员更改为

NeverCopy<Mutex> m_mutex;

不幸的是,使用该模板会丢失Mutex的特殊构造函数。

[edit]警告:“修复”复制CTor / asignment通常要求您在复制构造上锁定右侧,并在分配时锁定双方。不幸的是,没有办法覆盖复制ctor / assignment 调用默认实现,因此NeverCopy技巧可能不适用于没有外部锁定。 (还有一些其他解决方法有其自身的局限性。)

答案 1 :(得分:4)

如果某个对象是不可复制的,那么通常有充分的理由。如果有充分的理由那么你就不应该通过将它放入试图复制它的容器中来破坏它。

答案 2 :(得分:4)

如果它们是不可复制的,容器必须存储(智能)指向那些对象或引用包装器等的指针,尽管使用C ++ 0x,不可复制的对象仍然可以移动(如boost线程),这样它们可以按原样存储在容器中。

给出示例:引用包装器(又名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));
}

编辑:没关系,C ++ 0x FCD在30.4.1 / 3下说,

  

Mutex类型不可复制也不可移动。

所以你最好指点一下。智能或必要时包装。

答案 3 :(得分:2)

STL容器在很大程度上依赖于它们的内容是可复制的,因此要么将它们复制,要么不将它们放入容器中。

答案 4 :(得分:2)

对于你如何构建它的问题,没有真正的答案。没有办法做你想要的。实际的答案是容器包含指针,并且你已经说过一些未指明的原因是不行的。

有些人谈到了可移动的东西并使用C ++ 0x,其中容器通常要求其元素可移动,但不要求它们是可复制的。我认为这也是一个糟糕的解决方案,因为我怀疑互斥锁在被抓住时不应该被移动,这使得移动它们实际上是不可能的。

因此,唯一真正的答案就是指向互斥体。使用::std::tr1::shared_ptr(在#include <tr1/memory>)或::boost::shared_ptr指向互斥锁。这需要更改其中包含互斥锁的类的定义,但听起来你无论如何都要这样做。

答案 5 :(得分:1)

最好的选择是使用指针或某种类型的包装器类,它们使用引擎盖下的指针。这将允许这些被理智地复制,并且实际上做副本预期要做的事情(共享互斥锁)。

但是,既然你说没有指针,还有另外一个选择。听起来互斥锁“有时是可复制的”也许您应该编写一个复制构造函数和赋值运算符,如果在初始化后复制互斥锁,则会抛出异常。缺点是在运行时之前无法知道你做错了。

答案 6 :(得分:1)

使用boost :: shared_ptr等智能指针或使用另一个容器,如boost :: intrusive。两者都需要修改你的代码,通过。

答案 7 :(得分:0)

在类中使用互斥锁并不一定意味着该类必须是不可复制的。你可以(几乎)总是这样实现它:

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

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

虽然这使得有可能使用互斥锁复制类,但您可能不应该这样做。如果没有每个实例的互斥锁,你的设计会更好。

答案 8 :(得分:0)

在Ubuntu 14.04上使用c ++ 11(包括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());
    }
  }
};