14

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?

dbbd
  • 864
  • 1
  • 8
  • 23

10 Answers10

11

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.)

peterchen
  • 40,917
  • 20
  • 104
  • 186
4

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.

Cubbi
  • 46,567
  • 13
  • 103
  • 169
  • For most C++0x containers, the objects don't even have to be movable; `emplace` can construct them in place. They do need to be movable for `vector` and `deque`, since these need to reallocate memory as they grow. – Mike Seymour Aug 11 '10 at 11:08
  • 1
    Also, the C++0x accepted way of removing the copy constructor is `Moveable(const Movable &) = delete;`. – Omnifarious Aug 11 '10 at 11:31
  • This is really interesting, but I suspect that if a mutex is moved while it's held, something really bad might happen with some implementations. – Omnifarious Aug 11 '10 at 11:40
  • @Omnifarious: Why? Moving an OS object like a mutex simply involves copying some pointers. The OS doesn't even know what's going on. Also, rvalue references do not need to be const- it's in their definition that the point of them is to modify them. – Puppy Aug 11 '10 at 11:53
  • @DeadMG - So, what about all the `this` pointers that are referring to the original object's location? And how about if the mutex is implemented so that its address is how the OS identifies it? – Omnifarious Aug 11 '10 at 12:01
  • @Omnifarious: thanks, edited to look more C++0x-ish.. and then looked up C++0x FCD and found out that "A Mutex type shall not be copyable nor movable." (30.4.1/3). oops – Cubbi Aug 11 '10 at 13:07
  • hi, sorry for bothering , is the reference wrapper method supposed to work for this case? http://stackoverflow.com/questions/33778539/c-matrix-template-issue-with-stl-vector?noredirect=1#comment55329491_33778539 – lorniper Nov 18 '15 at 15:15
3

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.

pauljwilliams
  • 19,079
  • 3
  • 51
  • 79
  • 6
    This is a good general piece of advice, so I'm not downvoting you. But it's also a non-answer, so I'm sorely tempted to. – Omnifarious Aug 11 '10 at 10:56
  • 6
    In deed, it is an answer. What DBBD has asked is wrong. Visage tells him what is wrong: claiming objects are non-copyable but using them in a container that copies them. It is non-sense. Moreover DBBD doesn't even tell why using pointers is not acceptable, whereas it IS the way it should be, (most likely). – Stephane Rolland Aug 11 '10 at 11:10
  • 1
    @Omnifarious: Some people just ask wrong questions. Visage is IMHO right. – wilx Aug 11 '10 at 11:15
  • 1
    @wilx - I think that an answer to a 'wrong question' is an explanation, not a simple admonition. – Omnifarious Aug 11 '10 at 11:39
  • 6
    +1 Omnifarious. This is a non-answer. It doesn't even help the OP figure out *what his problem is*, it just tells him "you are stupid." ---- Even if a mutex is noncopyable, why should an object aggregating the mutex be? (*Answer: because it's the default*). In my experience, the solution here is usually to fix copy + assignment, rather than to "don't copy". – peterchen Aug 11 '10 at 16:45
  • The question is a good one, and it's more subtle than you might think. For example, suppose you have a map called `myMap` of this type: `std::map`. You might expect that the statement `myMap[0];` to be okay, but it won't compile using most STL implementations, because the `operator[]` insists on constructing the value first and then inserting it, instead of simply constructing it _in place_ where it will finally reside in the data structure. Conceptually, `myMap[0];` doesn't seem to require any copying, but it nonetheless results in a (needless) copy, and a compiler error. – Stuart Berg Sep 20 '11 at 00:05
  • The question is not wrong at all! I have met the same situation in a pretty different case in which I have a type that shouldn't be copyable and I need container that can deal with non copyable objects (preferably one that works with iterators) or a trick to convert my object into a type that can be passed and handled to standard data structures. The fact that an object is non-copyable shouldn't enforce you not to use a list or a vector of them! – Sheric Jun 21 '15 at 07:31
2

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.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • Moving an object like a mutex is a question of moving some OS-provided pointers, nothing more. You can't move an actual mutex, but it's trivial to move the OS-provided handle to it. – Puppy Aug 11 '10 at 11:55
  • @DeadMG - If the mutex is held or contended, that likely means there are `this` pointers pointing at the original object's location. Also, it's possible that the OS identifies the mutex by its memory address. – Omnifarious Aug 11 '10 at 12:03
1

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

user17481
  • 763
  • 6
  • 12
1

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.

SoapBox
  • 20,457
  • 3
  • 51
  • 87
1

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

blaze
  • 4,326
  • 18
  • 23
0

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());
    }
  }
};
matiu
  • 7,469
  • 4
  • 44
  • 48
0

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.

wilx
  • 17,697
  • 6
  • 59
  • 114
0

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);
    ...
};
  
wcochran
  • 10,089
  • 6
  • 61
  • 69