10

I'm reading the book C++ Concurrency in Action to learn more about threading and the C++ memory module. I'm curious about the number of times the copy constructor is called in the following code:

struct func
{
    func() = default;
    func(const func& _f) {}

    void operator()() {}
};

int main()
{
    func f;
    std::thread t{ f };
    t.join();

    return 0;
}

When I walk through this code in the Visual Studio 2013 debugger, I see the copy-constructor called four separate times. It's called three times from the main thread and then once from the new one. I was expecting one, as it made a copy of the object for the new thread. Why are three extra copies created?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Philip
  • 373
  • 2
  • 11
  • checked with g++ 4.8 and default is called once, and copy is done 2 times (http://coliru.stacked-crooked.com/a/cc470ded26d239a8). – marcinj Jan 02 '14 at 15:56
  • I just checked with Visual Studio 2012 and that one creates FIVE different versions of the object. So.... progress I suppose? – Philip Jan 02 '14 at 16:01
  • 2
    Since VC++ is notorious for eliding copies only in release mode: did you check the difference between debug vs. release mode? Also, you might want to instrument the destructor as this one safely shows how many objects are actually created. – Dietmar Kühl Jan 02 '14 at 16:10
  • @Philip -- If you haven't done so already, make sure that you are running an optimized release version using Visual Studio 2013, and not a "debug" build. Use the program that marcin_j provided at the link to get the proper number of times each function is called. – PaulMcKenzie Jan 02 '14 at 16:13
  • In your specific setup you can actually avoid all calls to a copy constructor of the function object: `std::thread t{ std::ref(f) };` Of course, that doesn't answer why your function object is copied as often as it is. – Dietmar Kühl Jan 02 '14 at 16:19
  • Both debug and release builds on a clang chain, (Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)), give the same result: one default construction, two copy constructions, and one `operator()`. And *both* copy-constructions are eliminated with `std::ref(f)` as the passed parameter. – WhozCraig Jan 02 '14 at 16:26
  • I stuck a std::cin statement into the copy constructor and ran it in optimized release. It still asked for four numbers, so it seems this happens when optimized too. – Philip Jan 02 '14 at 16:37
  • @DietmarKühl good call, thank you for pointing that out. I'm mostly curious as to why these constructors are being called in the first place, but workarounds are excellent to have in mind. – Philip Jan 02 '14 at 16:39
  • @WhozCraig: it is reasonable that compilers elide copy construction independent of the optimization level as copy elision introduces semantic changes. I would have guessed that copy elision would eliminate the copies on VC++, too (put Philip just updated with a statement saying it isn't). Based on that I would guess there is just somewhere a missing `std::forward()` or an argument is passed by value rather than deduced for a `T&&`. – Dietmar Kühl Jan 02 '14 at 16:39
  • maybe its one of the missing features, from this site: http://blogs.msdn.com/b/vcblog/archive/2013/12/02/c-11-14-core-language-features-in-vs-2013-and-the-nov-2013-ctp.aspx, i can read: "VS 2013 supported rvalue references, except for automatically generated move ctors/assigns.", so maybe that explains it. – marcinj Jan 02 '14 at 17:04

1 Answers1

1

If you set breakpoint in copy constructor you can see constructor call context in Call Stack window. In debug mode i found next points when constructor is called:

  • First off the functional object is copied in helper function bind

  • Then the functional object is moved into a internal functional object _Bind

  • After that a class for launching threads _LaunchPad is created. In
    a constructor it takes rvalue reference to the _Bind instance so we have
    another one move constructor call

  • move constructor of _LaunchPad is called when copy of it is created in new thread.

Thus we have 4 copy constructor call in your case. If you added move constructor you would see 1 copy constructor and 3 move constructor calls.

In release mode all empty constructor calls is elided and assembler code looks quite simple

sliser
  • 1,645
  • 11
  • 15