Click to See Complete Forum and Search --> : pointer to function in another class


elinap
January 14th, 2003, 05:36 PM
I have 3 classes that generate random numbers by different methods. Each of them has functions with the same name:
Init and Random.
A:Init() and A:Random()
B:Init() and B:Random()
C:Init() and C:Random()

I have another class where a calculation is performed. Depending on a local parameter (rng_type) in this class (which can be 1, 2 or 3) I would like to call:
if 1 call the A functions
if 2 call the B Functions
if 3 call the C Functions

Because the Random function will be called millions of times (!), I do not want to have to check the value of rng_type before each use.

Tried to do it with pointer to functions, but could not because the functions in the ABC classes are not static in the calculation class.

Any suggestions on how to do it would be much appreciated.
Eli

galathaea
January 14th, 2003, 07:58 PM
1. Use polymorphism. This seems a classic case. Just make a base class with virtual methods Init and Random and derive your three classes from them. Then where you set mg_type, instead make a base pointer pointing to A, B, or C. Call your methods from the base pointer.

or

2.

typedef void (A::*MyAFunctionPointer)();
MyAFunctionPointer initA = &A::Init;

// and to use (say you have myAInstance lying around)
(myAInstance.*initA)():

but I can't think how that would be any easier, since you still need to figure out which class instance to use and the member function pointer is tied to the class (unless there is polymorphism involved, in which case just use 1).

Evrardo
January 14th, 2003, 07:59 PM
Your description confused me a bit. What I understand is that your function random() functions are getting random numbers. I'm not quite sure what Init() does or if it's even important! Then you have your local rng_type, does it ever change? or does it stay the same throughout. From what you said, I believe it stays the same while the random functions do their millions of random numbers. So say rng_type is 1, does that mean that A:Random() is going to be called a lot of times and rng_type never changes? I'd like to help if I can, but I don't think I can help without further info...or maybe everybody else is catching onto something that I'm not! hehe

Kheun
January 14th, 2003, 10:20 PM
The state pattern (http://www.javaworld.com/javaworld/jw-08-1997/jw-08-stated.html) is a good solution if the value of rng_type is changing like a state machine.:)

KevinHall
January 15th, 2003, 03:31 PM
I have written a similar set of classes for random numbers. I used a single base class with pure virtual Random() and Seed() functions. Then I derived classes with different random number generators.

To best answer your question, you need to state what "rng_type" means and what your function is supposed to do for each value of "rng_type".

This "rng_type" variable looks like it is supposed to select the range of the random numbers. Granted I don't know what your application/library is supposed to do, so you can take the following comments with a grain of salt.

* Is "rng_type" supposed to generate random numbers:
- of uniform distribution over different ranges? (0 to 1, -1 to 1, ...)
- of different distributions? (uniform, gaussian, exponential, ...)

* Since different distributions can be generated from uniform deviates:
- My derived classes only generate uniform random deviates with a range of [0, 1)
- My base class defines functions for combinations of all other types of distributions and range limits.

Here's (part of) the header file for my random number library:


// Class that contains code to generate different random deviates
//
// This class is a virtual class that does not define how the standard
// uniform deviate is generated.  Because of this, useable random classes
// need to inherit the RandomBase class and define the functions:
//  Seed(), Seed(long), GetSeed(), CallCount(), CallMax(), Rand()
class RandomBase
{
   int     iset;
   double  gset;

public:
   RandomBase();

   // Seed random number generator
   virtual void    Seed() = 0;
   virtual void    Seed(long seed) = 0;

   // Retrieve the seed of a random number generator
   virtual long    GetSeed() = 0;

   // Returns the call count of the standard uniform deviate function Rand()
   virtual double          CallCount() = 0;

   // Returns the maximum call count of Rand() before correlations start
   // appearing in the random numbers
   virtual double          CallMax() = 0;

   // Returns a standard random uniform deviate between the numbers 0 and 1
   virtual double          Rand() = 0;

   // Returns a uniform deviate between the numbers min and max
   virtual double          Double(double min, double max);
   virtual unsigned long   ULong(unsigned long min, unsigned long max);
   virtual long            Long(long min, long max);

   // Returns a standard exponential deviate: -ln(Rand())
   // That is a deviate whose most probable value is 0 and whose probability
   // decays by 1/e over with every increment in range by 1.
   // In other words, the probability of receiving 2.0 is 1/e the probability of
   // receiving 1.0.
   virtual double          ExponentialDeviate();
   // Returns an exponential deviate whose most probable value is mostProbableValue
   // and whose probability decays by 1/e over decayLength. DecayLength can be negative.
   virtual double          Exponential(double mostProbableValue, double decayLength);
   // Returns an exponential deviate between min and max and whose probability
   // decays by 1/e over decayLength. DecayLength can be negative.
   virtual double          ExponentialRange(double decayLength, double min, double max);
   virtual unsigned long   ULongExponentialRange(double decayLength, unsigned long min, unsigned long max);
   virtual long            LongExponentialRange(double decayLength, long min, long max);

   // Returns a normally distributed deviate with zero mean and unit variance
   // (and unit standard deviation)
   virtual double          GaussianDeviate();
   // Returns a gaussian deviate with mean mean and a standard deviation of stdDev
   virtual double          Gaussian(double mean, double stdDev);
   // Returns a gaussian deviate with mean mean and a standard deviation of stdDev
   // IF the deviate lies between min and max.  NOTE: The measured mean and standard
   // deviation of these distrubutions will not be equal to the mean and stdDev inputs
   // due to the fact that parts of the distribution are "cut out"
   virtual double          GaussianRange(double mean, double stdDev, double min, double max);
   virtual unsigned long   ULongGaussianRange(double mean, double stdDev, unsigned long min, unsigned long max);
   virtual long            LongGaussianRange(double mean, double stdDev, long min, long max);

   // Special Distribution A: When this distribution is plotted on a semi-log graph
   // one will see a gaussian peaking at peak and whose half-maximum points are
   // halfOrder powers of 10 on each side of the peak.
   virtual double          SpecialA(double peak, double halfOrder);
   // Returns the a Special Distribution A deviate between the values of min and max
   virtual double          SpecialARange(double peak, double halfOrder, double min, double max);
   virtual unsigned long   ULongSpecialARange(double peak, double halfOrder, unsigned long min, unsigned long max);
   virtual long            LongSpecialARange(double peak, double halfOrder, long min, long max);
};

// A random class that will return 2 billion billion (2 * 10^18) standard
// uniform random deviates before correlations start appearing.
// (It would take 3GHz processors over 21 years to pass this many clock cycles)
//
// If no seed is specified to the constructor or Seed() function, then a
// combination of different system timers are used to generate the seed.
//
// For modern processors, integer manipulation is rather quick, and the speed
// at which this class generates numbers is usually negligible compared to the
// time of processes using the data.  There are quicker, less random generators,
// but for most applications, this class is the most appropriate to be used.  
class Random : public RandomBase
{
   unsigned long callCount;
   unsigned long callCountRollover;

   long    dummy;
   long    iy;
   long    iv[NTAB];

   long    seed;

public:
   Random();
   Random(long seed);

   void            Seed();
   void            Seed(long seed);

   long            GetSeed();

   double          CallCount();
   double          CallMax();

   double          Rand();
};

KevinHall
January 15th, 2003, 06:02 PM
Ok, I got a private message from eilnap and I understand what he is trying to do now. He has a couple a classes that generate random numbers and program startup, a value for rng_type is set from an ini file, and its value does not change while the app is running. Depending on what the value of rng_type is, he wants to select the Random Number Generator type (RNG).

Elinap, based on the info you sent me, here's what I would do:

(1) Write a base class TBaseRNG with pure virtual function Random().

(2) Derive specific implementations from this base class:

class TRanrotGenerator : public TBaseRNG;
class TRandomMotherGenerator : public TBaseRNG;
class TStdRNG : public TBaseRNG;

(3) Declare a variable of type (TBaseRNG):

TBaseRNG* pRng;

(4) Initialize that variable with different class instances depending on the value of rng_type:

switch(rng_type)
{
   case 0:
       pRng = new TRanrotGenerator(seed);
       break;
   case 0:
       pRng = new TRandomMotherGenerator(seed);
       break;
   default:
       pRng = new TStdRNG(seed);
}

(5) use the variable to generate random numbers:

randomValue = pRng->Random();

That's all. Hope this helps!

- Kevin

AnthonyMai
January 15th, 2003, 06:46 PM
Because the Random function will be called millions of times (!), I do not want to have to check the value of rng_type before each use.


It is my experience that whenever a random number generator is used, chances are you are going to repeatedly call it many times and you want it to be as fast as possible. This means you really shouldn't implement it as C++ classes. Instead you should do it in plain C or even assembly. Further, you may want to copy and paste the code to do it right in place instead of having to invoke the function call overhead.

None of other suggestions will save you any CPU cycles than the task of "have to check the value of rng_type". If any thing, those suggestions will only cost you more CPU cycles than a simple value check.

KevinHall
January 15th, 2003, 07:00 PM
I've used my random number generator classes hundreds of millions of times in tests and have profiled it. The hundreds of millions of calls takes less than a minute to execute. In all the applications I use random numbers for, the code that uses the random numbers takes much longer to run that the code that produces the random numbers -- in other words I would never notice a 10% or even 30% increase in speed of my random number generators. (That being said, I'm pretty darn sure I have a well optimized generator -- it beats both the MSVC's rand() and the Mersenned Twister, but still has excellent periods [thank you Numerical Recipes!!!]).

I'd have to say that the small overhead of using C++ classes with inheritance is now (with faster processors) is countered by having easy to read, portable code.

Anthony is right that assembly code or very optimized C code will be faster, but I believe for most applications there is no need to get that last 5 or 10% (guestimates here) improvement.

AnthonyMai
January 16th, 2003, 02:20 AM
I've used my random number generator classes hundreds of millions of times in tests and have profiled it. The hundreds of millions of calls takes less than a minute to execute. In all the applications I use random numbers for, the code that uses the random numbers takes much longer to run that the code that produces the random numbers -- in other words I would never notice a 10% or even 30% increase in speed of my random number generators. (That being said, I'm pretty darn sure I have a well optimized generator -- it beats both the MSVC's rand() and the Mersenned Twister, but still has excellent periods [thank you Numerical Recipes!!!]).


Oh, you call hundreds of millions of times per minute something fast? It's slow as crawing! For the record, up to twenty or even fourty hundred million instructions can be carried out NOT in one minute, BUT in one SECOND. So your random number generator takes more than a thousand clock cycles to generate just one random number. That's way too slow. I suspect that Microsoft rand() could be faster than yours.

The OP indicated that he wouldn't even want to check the value of one variable each time, which only takes a few clock cycles. One thousand clock cycles would be a much bigger deal.

rand() is nothing any close to be fast. MT is 4 times faster than rand() but is still not considered a fast PRNG at all. There are plenty of PRNGs out there that's way much faster than MT. Certainly the fastest solution would be a hardware based solution.

AnthonyMai
January 16th, 2003, 02:41 AM
Here is my test using rand() to generate one hundred million random numbers. I am using my old home computer, which is an "extremely slow" 500MHz machine. It takes 2.5 seconds.

Kevin probably has a 2GHz machine at work. If it takes him a minute to generate the same amount of random numbers, I guess he really has no reason to laugh at rand() and boast his own random number generator.

I am not saying rand() is fast. It is one of the slowest PTNG. But if you can't even beat rand(), you have to check your code again to see why it's so slow.

Believe me, there are lots of applications which needs zillions of zillions of random number fast. So speed is an important measurement of any good PRNG.

elinap
January 16th, 2003, 03:23 AM
Now the discussion gets interesting...

The call to the random generator gets executed in the main simulation loop in 6 different places (in the same class). The calculation loop runs for 2-10 million times, total generation ~ 100 million.

I think that having the code for the calculation pasted in the 6 different locations is not the solution.
But, taking what has been said, perhaps there is a speed advantage to using c functions and a pointer to function to select which calculation mode will be used.
The c++ class for the random generator is very simple: Initialize and Random, so it could be separated into c functions.

// Mother-of-all random number generator by Agner Fog 1998.
class TRandomMotherGenerator { // encapsulate random number generator
public:
void RandomInit(uint32 seed); // initialization
double Random(); // get floating point random number
TRandomMotherGenerator(uint32 seed=-1); // constructor
protected:
};

And the others are similar.

Eli

KevinHall
January 16th, 2003, 08:57 AM
For the record:

* I have a P3 750

* My test does more than just call random numbers, it must analyze them. Reread my earlier post (hundreds of millions, the code that uses the random numbers takes much longer to run that the code that produces the random numbers, and have profiled it).

* I'm not bragging about my generator, merely stating that it meets my needs, is portable, has good period, and is faster than some common generators. (I'm not complaining about the Mersenne Twister -- I believe it to be quite optimized, but it has a period much longer than I require.)

* Anthony Mai makes my point again. A hundred million random numbers don't take long at all. If you have a good generator, then squeezing the last bit of optimization out of it in most cases, will not usually not be noticed by a user.

Anyway, I think this discussion has reached its conclusion -- elinap has received the information he needs, and I think all other viewpoints have been expressed.

- Kevin

RNEELY
January 16th, 2003, 09:58 AM
What about the following?

// randomfun.cpp : fun with random numbers
// judicous use of the STL generate() and for_each() algorithms
// takes a bit to setup, but makes the main program very small
// and potentially quite fast

#include <iostream>
#include <conio.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>
using namespace std;

class Seed
{
unsigned int seed;
public:
Seed() : seed(0)
{
// could read seed from INI file here
srand(seed);
}
};

class Rand
{
static Seed seed; // the one an only seed for this random generator class
public:
int operator()()
{
// could use a different algorithm here
return rand();
}
};
// initalize statics
Seed Rand::seed;

class PrintOut
{
public:
void operator()(int i)
{
cout << i << endl;
}
};

int main(int argc, char* argv[])
{
cout << "Welcome to fun with random numbers" << endl;

// allocate an arrary jillion ints
const vector<int>::size_type AJILLION = 15;
vector<int> v(AJILLION);

// fill the array with a jillion random numbers
generate_n(v.begin(), AJILLION, Rand());

// print them out
for_each(v.begin(), v.end(), PrintOut());

cout << "Press any key to exit..." << endl << flush;
_getch();
return 0;
}




// Ron

galathaea
January 16th, 2003, 01:55 PM
But, taking what has been said, perhaps there is a speed advantage to using c functions and a pointer to function to select which calculation mode will be used.

The pointer to function can be used in c++ with static methods and state, as well. This will keep your encapsulation and make it easy to separate out various implementations. The polymorphism is merely function lookup in a vtable, and keeps the function table hidden from your own implementation details, which can be useful as well. It is a common misconception that c is faster than c++, but is not exactly true. Every alleged speed advantage of c has a corresponding object-oriented form in c++ with the advantage of type checking during translation (not at runtime). This is why driver development has been shifting to c++ in the past years.

Also, it is quite common for inexperienced assembly programmers to produce code less optimized than the compiler can. In the particular circumstance, however, it is quite common to make the c++ code first and compile, and then inline by hand (when the compiler doesn't know how) separate methods for each of the loops, possibly even unrolling the loop somewhat to lessen or remove the iteration checks. Assembly optimization should be done with a clock speed chart for the instruction set at hand to make calculations on execution advantages, needing in addition, a good understanding of the pipeline obstructions that can occur between registers and cache. Also, profiling and benchmarking should be done for all optimizations, to verify expected speed enhancements (as there can often be cache misses that interrupt the flow of execution).

AnthonyMai
January 16th, 2003, 02:07 PM
What about the following?


code:--------------------------------------------------------------------------------
// randomfun.cpp : fun with random numbers
// judicous use of the STL generate() and for_each() algorithms
// takes a bit to setup, but makes the main program very small
// and potentially quite fast


It's a total joke wrapping around rand() and call it STL based PRNG, and even "quite fast".

The problem with your class is it is state-less. A stateless class can NOT generate independent pseudo random number on it's own. You may create multiple instance of your class, think each of them generate independent random sequences. But underneath the number all come from the same rand() so they are all part of the same random sequence. Worse of all, creation of each instance of your class would reset your random sequence to start over with the same number.

Stateless classes are seldom useful at all. If you have a class that doesn't carry any data member that stores any useful information, chances are the class is not quite useful. Classes are supposed to encapsulate data. When it doesn't encapsulate any data at all, such classes are seldom meanful.