views:

375

answers:

2

Out of curiosity, I thought I'd try and write a basic C++ class that mimics C#'s multiple delegate pattern. The code below mostly does the job, with the nasty sacrifice of losing almost all type-safety, but having to use the initial dummy parameter to set up the va_list really seems a bit off. Is there a way to use va_list without this? I do realize there are ways to do this with (for example) boost, but I was aiming for something dead simple that used just the standard library.

#include <vector>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <algorithm>

using namespace std;

class CDelegate
{
public:
 virtual bool operator()(va_list params) = 0;
};

class CMultipleDelegateCaller
{
public:
 typedef vector<CDelegate*> CDelegateVector;

 CMultipleDelegateCaller& operator+=(CDelegate &rDelegate)
 {
  m_apDelegates.push_back(&rDelegate);
  return (*this);
 }

 CMultipleDelegateCaller& operator-=(CDelegate &rDelegate)
 {
  CDelegateVector::iterator iter = 
   find(m_apDelegates.begin(), m_apDelegates.end(), &rDelegate);
   if (m_apDelegates.end() != iter) m_apDelegates.erase(iter);
  return (*this);
 }

 bool Call(int iDummy, ...)
 {
  va_list params;
  CDelegate* pDelegate;
  CDelegateVector::iterator iter;
  for (iter = m_apDelegates.begin(); iter != m_apDelegates.end(); ++iter)
  {
   pDelegate = *iter;
   va_start(params, iDummy);
   if (!(*pDelegate)(params)) return false;
   va_end(params);
  }
  return true;
 }

private:
 CDelegateVector m_apDelegates;
};

class CTestDelegate:
 public CDelegate
{
public:
 CTestDelegate():m_iId(++s_iCount) {}
 virtual bool operator()(va_list params)
 {
  int iIntParam = va_arg(params, int);
    char* szCharPtrParam = va_arg(params, char*);
   string* psStringParam = va_arg(params, string*);
  cout<<m_iId<<"{"
   <<iIntParam<<", "
   <<szCharPtrParam<<", "
   <<*psStringParam<<"}"<<endl;

  return true;
 }
 int m_iId;
 static int s_iCount;
};
int CTestDelegate::s_iCount = 0;

int main(int argc, char* argv[])
{
 CMultipleDelegateCaller cDelegateCaller;
 CTestDelegate cTestDelegate1;
 CTestDelegate cTestDelegate2;

 cout<<"--------------------"<<endl;
 cDelegateCaller += cTestDelegate1;
 cDelegateCaller += cTestDelegate2;
 string sString("World");
 cDelegateCaller.Call(1, 2, "Hello", &sString);
 cout<<"--------------------"<<endl;
 cDelegateCaller -= cTestDelegate1;
 cDelegateCaller.Call(1, 2, "Hello", &sString);
 cout<<"--------------------"<<endl;
 cDelegateCaller -= cTestDelegate2;
 cDelegateCaller.Call(1, 2, "Hello", &sString);
 cout<<"--------------------"<<endl;

 cin>>sString;
 return 0;
}
+2  A: 

Functions with ellipsis in C++ is only for compatibility with C. Using C++ I'd return temporary helper object in Call function and add template operator% to pass variable number of arguments. To use it in the following way:

cDelegateCaller.Call() % 2 % "Hello" % sString; // dummy argument isn't required

As to your question, Standard requires to invoke va_start before any access to the unnamed arguments. And va_start requires second argument which is the identifier of the rightmost parameter in the variable parameter list in the function definition.

Kirill V. Lyadvinsky
How would this then be used to call the function? Call would be executed before each of the modulus operators, so the parameters would not have been passed in yet.
FlintZA
Actual action will take place in destructor of temporary object that `Call` function will return. You could find sample here - http://stackoverflow.com/questions/1394053/how-to-write-a-generic-alert-message-using-win32/1394069#1394069
Kirill V. Lyadvinsky
Ok, so referencing the above question, and the one provided by xtofl, I have an internal helper which would be returned by Call, and has an operator% that adds arguments to an internal arglist (as defined by xtofl (http://stackoverflow.com/questions/1350657/variable-parameter-function-how-to-make-it-type-safe-and-more-meaningful/1361620#1361620). In it's destructor it iterates over CDelegateCaller's delegate list, but how would these arguments be passed to the delegate operator() in such a way that the user doesn't need to implement multiple recursive functions?
FlintZA
Is that a problem to invoke `operator()` on each `operator%` call? Or it is required to pass all arguments at once? In the sample you have posted no need in temporary object. Function `Call` could return reference to `this` and call `operator()` on every `operator%` call.
Kirill V. Lyadvinsky
Yes, the idea is to be able to call a single delegate function with all the parameters. The simple test case could be implemented with multiple calls, since it's just outputting a string, but the actual intention is to call more complex functions.
FlintZA
A: 

Out of Kirill's answer you can conclude that it's possible to create a type-safe delegate, using a template argument-combining function. This function also needs a dummy starting point, but has the benefit of type-safety.

The FastFormat library uses this, boost uses this, and I once provided another example in an answer to another question.

xtofl
Thanks, I've had a look at your ArgList templates, see the comments on Kirill's answer..
FlintZA