Skip to main content

C++11 std::thread Construction and Assignment Tricks





C++11 arrived with std::thread and related classes built within the standard. With the move semantics in place, the construction and assignment practices of 'std threads' deserve a small write up.

Let's define a 'Runnable' first:

class Runnable
{
   public:
      virtual void operator ()() = 0;
      virtual ~Runnable(){}
};


class Task : Runnable
{
     public:
         void operator ()()
         {
              //do sth here..
         }
};
                                         

      //later..

     #include <thread>

    Task runnable;

    std::thread t1; //not attached to any thread  
                                         
    std::thread t2 ( runnable ); //attached and running

 

Copy Construction of std::thread is not allowed


           std::thread t3 ( t2 ); // compilation error

           std::thread t4 = t2; // compilation error

error : use of deleted function std::thread (const std::thread&); 

This is because the copy constructor has been marked as deleted in C++11.

Assignment is also not allowed


           t1 = t2; // compilation error

error : use of deleted function std::thread& operator=(const std::thread&);

This is because the assignment operator has been marked as deleted in C++11.


Construction with move semantics is the only way

      

           std::thread t5 ( std::move ( t2 ) );  // ok
                                         
                           or
                                     
           std::thread t6 = std::move ( t2 ) ; // ok

                          or

           std::thread t6 ( std::thread (task) ) ; // ok - temporaries don't require explicit 'move'

                         or

          std::thread t6  = std::thread (task) ; // ok - temporaries don't require explicit 'move'

All this is possible because std::thread class has folllowing constructors defined:


thread()

     // not attached
thread( thread&& other );
     // with move semantics - takes ownership from passed ref



                                         

Assignment with move semantics is also allowed


                          std::thread t7;

                          t7 = std::move ( t2 ); // ok

                             or

        t7 = std::thread ( task ); // ok - temporaries don't require explicit 'move'

After all these invocations, only 't7' is referring to underlying thread and 't2' has relinquished ownership of underlying.

What if 't7' already had a thread attached ?


                          std::thread t7 ( anotherTask ) ;    

                          t7 = std::move ( t2 );

The real thread attached to 't7' will get terminated (if still alive) because there is no 'visible' handle to it anymore. That will cause unexpected effects and the program may crash.

If real thread was alive, C++ run-time will invoke std::terminate which calls the currently installed std::terminate_handler. The default std::terminate_handler calls std::abort.  Which creates a core dump with program termination.

To avoid this situation, properly 'detach' the thread or 'wait' for it to die.

                          std::thread t7 ( sometask ) ;
                          t7.detach();

This will leave the real thread running as a daemon and now 't7' can become a handle to another real thread as follows:

                          t7 = std::move ( t2 );


 References:

http://en.cppreference.com/w/cpp/thread/thread/thread    

The images on this page are of Emperor SamudraGupta, one of the greatest Indian kings, warrior and a patron of art circa 335 - 375 CE.

More details here: http://en.wikipedia.org/wiki/Samudragupta

Comments

Popular posts from this blog

C++ logging using Apache Log4cxx on Linux

I'd demonstrate the set up of Apache log4cxx on Ubuntu 12.x in following steps: 1. Download all required packages 2. Build Log4cxx via Apache Maven 3. Use the libraries in a NetBeans C++ project on Ubuntu Linux. Reference URL: http://www.yolinux.com/TUTORIALS/Log4cxx.html This URL is quite detailed and explains things for other *nix operating systems as well. I wanted to start with minimum steps required, hence this post. I have a Windows 7 desktop and have Ubuntu 12.x 64-bit running on it via Oracle Virtualbox. So Ubuntu is running as a guest on my Windows 7 host. [The reference URL mentions different versions of  'libaprXX' libs but we have to use 'libapr1-dev' and 'libaprutil-dev', will see that later.] Prerequisites (install all of the following one by one) autoconf and automake --> sudo apt-get install autoconf automake libxml2 and libxml2-devel ( http://xmlsoft.org/ ) --> sudo apt-get install libxml2 libxml2-dev gmp ( ...

C++ Initialization Subtleties (significance of -Werror compiler option)

Modern C++ continuous integration build pipelines might produce huge logs which may be easily overlooked. Among other errors/warnings, a potential risk caused by invalid narrowing assignments might be lurking in those dark corners... This write up is a little reminder about an essential feature in modern C++ compilers and can help defend against that specific problem. Prior to C++11, the following was a valid assignment and still is even in C++20 or higher ... unsigned int unsignedSinceBirth = 10; unsignedSinceBirth = -10;  // assigning a negative value to an unsigned container printUnsignedVal(unsignedSinceBirth); which when compiled using these options "g++ -std=c++20 <cpp files> -o <executable-name>" does not even emit a warning. And an executable is generated successfully. The output of running that code is an arbitrary unexpected value. Modern C++ (post Cpp11) allow uniform initialization syntax which can help the compiler detect this situation as follows: ...