Search code examples
c++c++11packaged-taskcasyncsocket

Using std::packaged_task to queue CAsyncSocket-Detach-socket tasks causes compile error when non-static Attach is called from static method


I am implementing code so I can Accept network connections as they arrive, Detach them from their arrival socket, create an std::packaged_task task, queue that task in a deque container, and then execute those tasks in their task thread later. Making that easy is Bo Qian’s YouTube lecture on “C++ Threading #9: packaged_task” which shows how to do this.

#include "stdafx.h"
#include <afxsock.h>
#include <condition_variable>
#include <deque>
#include <future>

std::condition_variable notifyDequeNotEmptyCondVar;
std::mutex decodeMu;

class MyRxDecode : public CAsyncSocket
{
public:
  static std::deque< std::packaged_task< bool() > > rxAcceptedTasks;

  static bool StartDecode( SOCKET socket )
  {
    bool result = true;

    // Attach detached socket to this socket
    //result = Attach( socket ); //  error C2352: 'CAsyncSocket::Attach': illegal call of non-static member function
    return result;
  }

  static bool DecodeTaskThread()
  {
    std::packaged_task< bool() > DecodingTask;

    {
      std::unique_lock< std::mutex > dequeLocker( decodeMu ); // makes sure all deque actions are atomic
      notifyDequeNotEmptyCondVar.wait( dequeLocker, [] () { return !rxAcceptedTasks.empty(); } ); // wait until notified that deque is not empty
      DecodingTask = std::move( rxAcceptedTasks.front() );
      rxAcceptedTasks.pop_front();
    }

    DecodingTask(); // has no arg because the arg was previously bound to the functor passed in

    return true;
  }
};


class MyListener : CAsyncSocket
{
  virtual void OnAccept( int nErrorCode ) // is called when other socket does a connect on this socket's endpoint
  {
    CAsyncSocket syncSocket; // msdn prescribes creating stack socket
    if( Accept( syncSocket ) )
    {
      AsyncSelect( FD_READ | FD_CLOSE ); // msdn
      SOCKET socket = syncSocket.Detach(); // msdn

      // Bo Qian's lecture explains how this packaged task code works and is made thread safe.
      // Create task in separate thread to process this connection and push onto deque. The main advantage of a packaged task compared to using a functor is the former links the callable object to a future, which is useful in a multi-threaded environment (Bo Qian).
      std::thread decodeThread( MyRxDecode::DecodeTaskThread ); // pass-by-value ctor
      std::packaged_task< bool() > rxAcceptTask( std::bind( MyRxDecode::StartDecode, socket ) ); // binds function with its param to create functor wh is passed to packaged task's ctor
      std::future< bool > rxAcceptTaskFuture = rxAcceptTask.get_future();

      {
        std::lock_guard< std::mutex > locker( decodeMu );
        MyRxDecode::rxAcceptedTasks.push_back( std::move( rxAcceptTask ) );
      }
      notifyDequeNotEmptyCondVar.notify_one();
      bool taskResult = rxAcceptTaskFuture.get();
      decodeThread.join();

      CAsyncSocket::OnAccept( nErrorCode ); // msdn
    }
  }
};

std::deque< std::packaged_task< bool() > > MyRxDecode::rxAcceptedTasks;

int main()
{
  return 0;
}

The code does not compile in my case because my StartDecode is a static method trying to call a non-static Attach. StartDecode is a static method because std::bind is used to bind ‘socket’ to the task. ‘socket’ would normally be passed in to the StartDecode method, but in order for 'future' in the packaged task to work properly, any passed-in parameters have to be bound ahead of time using std::bind. But once StartDecode is made static, a call to CAsyncSocket’s Attach, which is non-static, causes error C2352.

How can I call the non-static Attach method from static MyRxDecode::StartDecode? Is there a way to avoid having to bind the socket param to the task without making it static?


Solution

  • std::bind lets you call non-static member functions. See cppreference.com->std::bind:

    Notes
    … when invoking a pointer to non-static member function or pointer to  
    non-static data member, the first argument has to be a reference or pointer (including, 
    possibly, smart pointer such as std::shared_ptr and std::unique_ptr) to an object whose 
    member will be accessed. 
    

    Replacing the definition for rxAcceptTask in the code above with the following:

    std::packaged_task< bool() > rxAcceptTask( std::bind( &MyRxDecode::StartDecode, &myRxDecode, socket ) );
    

    lets the bool StartDecode( SOCKET socket ) member function become non-static.