3

I apologize if this has been asked before, I was not able to find it online. Why does the compiler think that I am trying to call the copy constructor of std::condition_variable?

#include <iostream>
#include <utility>
#include <vector>
#include <memory>
#include <condition_variable>
using namespace std;

class A {
 public:
  A() = default;
  A(A&&) = default;
  A& operator=(A&&) = default;
  A(const A&) = delete;
  A& operator=(const A&) = delete;
};
int main() {

  std::vector<std::shared_ptr<std::condition_variable>> m;
  m.push_back(std::make_shared<std::condition_variable>(std::condition_variable{}));

  // no complains here
  std::vector<std::shared_ptr<A>> m_a;
  m_a.push_back(std::make_shared<A>(A{}));

  return 0;
}

The error I get is that I am trying to use the deleted copy constructor of std::condition_variable.. I guess what I am trying to ask is why the move constructor is not called with that invocation of make_shared

Wyetro
  • 8,439
  • 9
  • 46
  • 64
Curious
  • 20,870
  • 8
  • 61
  • 146
  • 1
    The arguments for `make_shared` are used fo constructing the object. In your case you are passing it a temporary which matches the copy constructor signature. – Arunmu Jul 06 '16 at 19:24
  • @Arunmu Actually, it matches move constructor signsture – UldisK Jul 06 '16 at 19:25
  • @UldisK There is no `move constructor` for condition_variable. – Arunmu Jul 06 '16 at 19:26
  • @UldisK if the copy constructor is explicitly defined as deleted, the move constructor will not be implicitly declared. – Brian Bi Jul 06 '16 at 19:26

2 Answers2

9

In

std::make_shared<std::condition_variable>(std::condition_variable{})

std::condition_variable{} creates a std::condition_variable. This means that std::make_shared is going to construct it's internal std::condition_variable with the passed parameter which invokes the copy constructor. If you need a default constructed std::condition_variable then you can use

std::make_shared<std::condition_variable>()
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Why not the move constructor? – Curious Jul 06 '16 at 19:25
  • 3
    @Curious It does not have a move constructor. – NathanOliver Jul 06 '16 at 19:25
  • that's right! I dont know why I confused move assignment with the move construction – Curious Jul 06 '16 at 19:27
  • 1
    @Curious It's not move assignable as well :) – Arunmu Jul 06 '16 at 19:29
  • @Arunmu it has to be, how else can you have a vector of condition variables? At least I'm pretty sure it is – Curious Jul 06 '16 at 19:30
  • @Curious per [this](http://en.cppreference.com/w/cpp/thread/condition_variable) and the standard it is not move assignable as well. – NathanOliver Jul 06 '16 at 19:31
  • So technically you are not allowed to have a vector of condition_variables? That is news to me............ – Curious Jul 06 '16 at 19:35
  • @Curious I don't believe so. I suppose you could if you do not need dynamic growth and instead reserve the size you need and then use `emplace_back` to add the `condition_variable`s – NathanOliver Jul 06 '16 at 19:37
  • Wait I think the reserve and `emplace_back` trick might not work, since space is allocated right when the vector calls `new` and that will call the default constructor for `condition_variable`s which means that any subsequent insert will move the object into place right? `emplace_back`ing is quite similar to `push_back`ing with an rvalue – Curious Jul 06 '16 at 19:39
  • I think the only way it would be possible would be if a vector allocates the right amount of memory and does a placement new. But that does not sound quite right.. – Curious Jul 06 '16 at 19:41
  • @Arunmu and NathanOliver Maybe I should ask this as another question? – Curious Jul 06 '16 at 19:42
  • @Curious As long as you do not out grow the capacity of the vector it has no reason to allocate more space. So if you need 50 variables you call `reserve` with 50 and then you can `emplace_back` 50 variables without a reallocation. Not sure if the standard guarantees `size == capacity()` but it should work on any sane implementation. – NathanOliver Jul 06 '16 at 19:42
  • @Curious Another question about that would be great. I find nothing with a quick google search. – NathanOliver Jul 06 '16 at 19:42
  • But when you do a `reserve` you are essentially calling the `new` operator to allocate memory for `x` objects on the heap right? that would call the default consturctor for those objects? – Curious Jul 06 '16 at 19:43
  • I'm not sure. Good food for though for another question ;) – NathanOliver Jul 06 '16 at 19:44
  • http://stackoverflow.com/questions/38232576/why-is-a-condition-variable-not-moveassignable :) – Curious Jul 06 '16 at 19:52
  • @Curious: `reserve` is not enough, as *resize* code has to handle the case where there is not enough space, the only way is at the construction. – Jarod42 Jul 06 '16 at 20:00
  • @NathanOliver That doesn't work, because whether or not `emplace_back()` or `reserve()` has to move/copy items is a run time decision, not a compile time decision, so code has to be generated that can move/copy the items held. The only way to put a non-moveable/non-copyable type into a `vector` is at `vector` construction time when using the `explicit vector(size_type n, const Allocator& = Allocator());` constructor, as that uses default construction to put elements into the `vector`. – Nevin Jul 06 '16 at 20:25
4

Why does the compiler think that I am trying to call the copy constructor of std::condition_variable?`

That's what the compiler will use when you call

std::make_shared<std::condition_variable>(std::condition_variable{})

std::make_shared uses any constructor that will take the arguments passed to it. In this case, the copy constructor is the one.

From http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared:

args - list of arguments with which an instance of T will be constructed.

R Sahu
  • 204,454
  • 14
  • 159
  • 270