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

发布于 2024-09-14 05:11:01 字数 238 浏览 9 评论 0原文

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

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

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

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

有什么建议吗?

I'm looking for the best-practice of dealing with non-copyable objects.

I have a mutex class, that obviously should not be copyable.
I added a private copy constructor to enforce that.

That broke the code - some places simply needed to be fixed, but I have a generic problem
where a class, using the mutex either as a data member, or by inheritance, is being inserted into a container.

This is usually happening during the container initialization, so the mutex is not initialized yet, and is therefore ok, but without a copy constructor it does not work. Changing the containers to contain pointers is not acceptable.

Any advise?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

寒尘 2024-09-21 05:11:02

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 ,它可以保存不可复制的对象(例如上面 Foo 的第一个版本的实例通常使用 emplace_back 方法“就地”构造对象来添加元素 - 例如,
这里 Foo 类型的对象不需要需要可复制:

struct FooPool {
    std::deque<Foo> objects;
    ObjectPool(std::initializer_list<T> argList) {
        for (auto&& arg : argList)
             objects.emplace_back(arg);
    ...
};
  

std::vector can not store non-copyable objects (due to resize) thus you can't store objects of type Foo:

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

One way around this is to use std::unique_ptr:

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

Another option is to use a std::deque which can hold non-copyable objects (like instances of the first version of Foo above. Typically use emplace_back method to construct objects "in place" to add elements -- no copies happen. For example,
objects of type Foo here do not need to be copiable:

struct FooPool {
    std::deque<Foo> objects;
    ObjectPool(std::initializer_list<T> argList) {
        for (auto&& arg : argList)
             objects.emplace_back(arg);
    ...
};
  
隔岸观火 2024-09-21 05:11:02

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

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

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

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

Using a mutex in a class does not necessarily mean that the class has to be non-copyable. You can (almost) always implement it like this:

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

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

While this makes it somewhat possible to copy classes with mutexes, you probably should not do it. Chances are that your design will be better without per-instance mutex.

我不在是我 2024-09-21 05:11:02

在 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());
    }
  }
};

Using c++11 on Ubuntu 14.04 (which includes emplace_back), I've gotten this to work.

I found that emplace_back worked fine, but erase (and probably insert) didn't work because, when the vector was shuffling the elements along to fill in the gap, it mas using:

 *previous = *current;

I found the trick was allowing move assignment in my resource class:

  Watch& operator=(Watch &&other);

This is my inotify_watch class, which can live in a 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());
    }
  }
};
笑咖 2024-09-21 05:11:01

这里提供三种解决方案:

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;

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

[编辑]警告:“修复”复制CTor/赋值通常需要您锁定复制构造的右侧,并锁定赋值的两侧。不幸的是,没有办法覆盖复制构造函数/赋值并调用默认实现,因此如果没有外部锁定,NeverCopy 技巧可能对您不起作用。 (还有一些其他解决方法也有其自身的局限性。)

Three solutions here:

1. Use Pointers - The quick fix is to make it a container of pointers - e.g. a shared_ptr.

That would be the "good" solution if your objects are truly noncopyable, and using other containers is not possible.

2. Other containers - Alternatively, you could use non-copying containers (that use in-place-construction), however they aren't very common and largely incompatible with STL. (I've tried for a while, but it's simply no good)

That would be the "god" solution if your objects are truly noncopyable, and using pointers is not possible.

[edit] With C++13, std::vector allows inplace construction (emplace_back), and can be used for noncopyable objects that do implement move semantics.
[/edit]

3. Fix your copyability - If your class is copyable as such, and the mutex is not, you "simply" need to fix the copy constructor and assignment operator.

Writing them is a pain, since you usually have to copy & assign all members except the mutex, but that can often be simplified by:

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

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

And changing you mutex member to

NeverCopy<Mutex> m_mutex;

Unfortunately, using that template you lose special constructors of Mutex.

[edit] Warning: "Fixing" the Copy CTor/asignment often requires you to lock the right hand side on copy construct, and lock both sides on assignment. Unfortunately, there is no way to override the copy ctor/assignment and call the default implementation, so the NeverCopy trick might not work for you without external locking. (There are some other workarounds with their own limitations.)

梦行七里 2024-09-21 05:11:01

如果它们是不可复制的,则容器必须存储指向这些对象的(智能)指针或引用包装器等,尽管使用 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 下,

互斥体类型不可复制或移动。

所以你最好使用指向它们的指针。根据需要进行智能或其他包装。

If they are non-copyable, the container has to store (smart) pointers to those objects, or reference wrappers, etc, although with C++0x, noncopyable objects can still be movable (like boost threads), so that they can be stored in containers as-is.

to give examples: reference wrapper (aka boost::ref, a pointer under the hood)

#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, tested with 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, C++0x FCD says, under 30.4.1/3,

A Mutex type shall not be copyable nor movable.

So you're better off with pointers to them. Smart or otherwise wrapped as necessary.

千里故人稀 2024-09-21 05:11:01

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

If an object is non-copyable then there is usually a good reason. And if there's a good reason then you shouldnt subvert that by putting it into a container that attempts to copy it.

勿忘心安 2024-09-21 05:11:01

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

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

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

There is no real answer to the question given how you've framed it. There is no way to do what you want. The actual answer is for the container to contain pointers, and you've said that isn't OK for some unspecified reason.

Some have talked about things being movable and using C++0x in which containers often require their elements to be movable, but do not require them to be copyable. I think this is a poor solution as well because I suspect that mutexes should not be moved while they are held, and this makes it effectively impossible to move them.

So, the only real remaining answer is to point at the mutexes. Use ::std::tr1::shared_ptr (in #include <tr1/memory>) or ::boost::shared_ptr to point at the mutexes. This requires changing the definitions of the classes that have the mutexes inside them, but it sounds like you're doing that anyway.

夜吻♂芭芘 2024-09-21 05:11:01

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

STL containers rely heavily on their contents being copyable, so either make them copyable or do not put them into container.

风轻花落早 2024-09-21 05:11:01

最好的选择是使用指针或某种类型的在底层使用指针的包装类。这将允许它们被合理地复制,并且实际上执行副本预期执行的操作(共享互斥锁)。

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

The best option is to use pointers or some type of wrapper class that uses pointers under the hood. That would allow these to be sanely copied, and actually do what a copy would be expected to do (share the mutex).

But, since you said no pointers, there is one other option. It sounds like Mutexes are "sometimes copyable" perhaps you should write a copy constructor and an assignment operator and have those throw an exception if a mutex is ever copied after it has been initialized. The down side is there's no way to know you're doing it wrong until runtime.

半边脸i 2024-09-21 05:11:01

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

Use smart pointers like boost::shared_ptr or use another containers, like boost::intrusive. Both will require to modify your code, through.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文