14

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

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

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

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

有什么建议吗?

4

10 回答 10

11

这里有三个解决方案:

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如果没有外部锁定,这个技巧可能对你不起作用。(还有一些其他的解决方法有其自身的局限性。)

于 2010-08-11T10:59:51.227 回答
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 类型不得复制或移动。

因此,您最好使用指向它们的指针。智能或必要时以其他方式包装。

于 2010-08-11T10:50:51.503 回答
3

如果一个对象是不可复制的,那么通常有一个很好的理由。如果有充分的理由,那么您不应该通过将其放入尝试复制它的容器来颠覆它。

于 2010-08-11T10:47:33.240 回答
2

考虑到你是如何构想的,这个问题没有真正的答案。没有办法做你想做的事。实际的答案是容器包含指针,并且您已经说过由于某些未指定的原因这是不正确的。

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

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

于 2010-08-11T11:46:19.080 回答
1

使用诸如 boost::shared_ptr 之类的智能指针或使用其他容器,例如 boost::intrusive。两者都需要通过修改您的代码。

于 2010-08-11T11:44:00.590 回答
1

STL 容器严重依赖于它们的内容是可复制的,因此要么使它们可复制,要么不将它们放入容器中。

于 2010-08-11T10:49:05.903 回答
1

最好的选择是使用指针或某种在底层使用指针的包装类。这将允许这些被合理地复制,并且实际上做一个副本应该做的事情(共享互斥锁)。

但是,既然你说没有指针,还有另一种选择。听起来互斥锁“有时是可复制的”,也许您应该编写一个复制构造函数和一个赋值运算符,如果在初始化互斥锁后曾经复制过它,则让它们抛出异常。不利的一面是直到运行时才知道你做错了。

于 2010-08-11T10:51:17.287 回答
0

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

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

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

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

于 2010-08-11T11:14:05.570 回答
0

在 Ubuntu 14.04(包括 emplace_back)上使用 c++11,我已经让它工作了。

我发现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());
    }
  }
};
于 2014-12-20T22:40:00.180 回答
0

std::vector不能存储不可复制的对象(由于调整大小)因此你不能存储类型的对象Foo

struct Foo {
   std::mutex mutex;
   ...
};

解决此问题的一种方法是使用std::unique_ptr

struct Foo {
   std::unique_ptr<std::mutex> pmutex;
   Foo() : pmutex{std::make_unique<std::mutex>()} {}
   ...
};

另一种选择是使用std::deque可以保存不可复制对象的 a (例如Foo上面第一个版本的实例。通常使用emplace_back方法来“就地”构造对象以添加元素 - 不会发生副本。例如,Foo这里类型的对象不需要是可复制的:

struct FooPool {
    std::deque<Foo> objects;
    ObjectPool(std::initializer_list<T> argList) {
        for (auto&& arg : argList)
             objects.emplace_back(arg);
    ...
};
  
于 2022-01-30T22:11:14.477 回答