bill s
bill s

Reputation:

How correctly organize shared class members?

Suppose I have a function y of n variables x[0]..x[n-1]:

y = 1 + a[0][0]*x[0] + a[1][0]*x[0]^2 + a[0][1]*x[1] + a[1][1]*x[1]^2 + ...

I want to find values of variables x[0]..x[n-1], which minimize y. This optimization task will be performed by a genetic algorithm. The basic steps of the algorithm are:

  1. Generate a pool (population) of random solutions x[0]..x[n-1] (also called chromosomes)
  2. Evaluate these random solutions y = F(x[0],..,x[n-1])
  3. Divide the pool solutions into two groups: those with low y (best solutions) and those with high y (poor solutions).
  4. Throw away poor solutions and breed the best ones by a crossover (swap individual elements x[j] of different solutions).
  5. Mutate new solutions by adding a random value to selected elements x[j]. We got a new pool of solutions.
  6. Repeat 2-5 until a stopping criteria is met.

Below is the code of two classes that implement the above algorithm:

Here is the code:

class Solution
{
    double *x;
    double y;
    double **a; // the same for all instances
    int n; // the same for all instances
public:
    Solution(int,double*);
    ~Solution();
    double yFunc();
}

class Pool
{
    vector<Solution> sols;
    int poolSize;
    // other private members
public:
    Pool(int,int,double*);
    ~Pool();
    // other public functions
}

// construct Solution
Solution::Solution(int numX,double **coef)
{
    n=numX;
    // allocate memory for coefficients
    a = new double*[2];
    for(int i=0;i<2;i++) a[i] = new double[n];
    // assign coefficients
    for(int i=0;i<2;i++) 
        for(int j=0;j<n;j++)
            a[i][j] = coef[i][j];
    // generate a random solution in [-1,+1] range
    srand(time(0));
    for(int j=0;j<n;j++)
        x[j] = 2.0*(rand()/(double)RAND_MAX-0.5);
}

// destroy Solution
Solution::~Solution()
{
    delete[] x;
    for(int i=0;i<2;i++) delete[] a[i];
    delete[] a;
}

// define optimized function
double Solution::yFunc()
{
    double sum=1.0;
    for(int j=0;j<n;j++)
        sum += a[0][j]*x[j]+a[1][j]*x[j]*x[j];
    return sum;
}

// construct Pool
Pool::Pool(int numSols, int numX, double **coef)
{
    poolSize = numSols;
    for(int i=0;i<poolSize;i++)
    {
        Solution sol = new Solution (numX,coef);
        sols.push_back(sol);
    }
}

I have only one question:

When I construct the pool of solutions, I create many instances of class Solution with their individual vectors x[0]..x[n-1] and the corresponding function value y. However, the a[][] coefficients of the function y = F(x[0],..,x[n-1]) and the function itself also get instantiated poolSize times even though they are the same for all solution vectors.

How can I modify the code such that only single instances of a[][],F(x[0],..,x[n-1]), and n are created? I heard about static members, but as I understand, a user is not allowed to assign their values through an external function call. These static members can only to be initialized within a class or a static Solution() function that does not except any arguments.

Even if there is a way to initialize these static members with external user-provided values, they are not multi-thread safe. So, I am looking for a way to separate the shared members of class Solution from the individual members, to avoid instantiation of the shared members poolSize times.

Upvotes: 2

Views: 1383

Answers (5)

Dave Gamble
Dave Gamble

Reputation: 4174

Looking at what you're trying to do, it might be simpler to regard the a/n values as being something else... How about:

class Target
{
public:
    double **a; // the same for all instances
    int n; // the same for all instances
    Target(int,double **);
    ~Target();
};

class Solution
{
    double *x;
    double y;
    Target *t
public:
    Solution(Target *t);
    ~Solution();
    double yFunc();
}

class Pool
{
    vector<Solution> sols;
    int poolSize;
    // other private members
public:
    Pool(int,int,double*);
    ~Pool();
    // other public functions
}

// construct Solution
Target::Target(int numX,double **coef)
{
    n=numX;
    // allocate memory for coefficients
    a = new double*[2];
    for(int i=0;i<2;i++) a[i] = new double[n];
    // assign coefficients
    for(int i=0;i<2;i++) 
        for(int j=0;j<n;j++)
                a[i][j] = coef[i][j];
}

Solution::Solution(Target *in_t)
{
    t=in_t;
    // generate a random solution in [-1,+1] range
    srand(time(0));
    for(int j=0;j<t->n;j++)
        x[j] = 2.0*(rand()/(double)RAND_MAX-0.5);
}

// destroy Solution
Target::~Target()
{
    for(int i=0;i<2;i++) delete[] a[i];
    delete[] a;
}

Solution::~Solution()
{
    delete[] x;
}

// define optimized function
double Solution::yFunc()
{
    double sum=1.0;
    for(int j=0;j<t->n;j++)
        sum += t->a[0][j]*x[j]+t->a[1][j]*x[j]*x[j];
    return sum;
}

// construct Pool
Pool::Pool(int numSols, int numX, double **coef)
{
    poolSize = numSols;
    Target target(numX,coef);

    for(int i=0;i<poolSize;i++)
    {
        Solution sol = new Solution (&target);
        sols.push_back(sol);
    }
}

Now you have a Target object, and a Solution, created with reference to the Target.

Upvotes: 0

Daniel Earwicker
Daniel Earwicker

Reputation: 116674

It sounds like you've picked up some C# or Java hearsay and got it confused with C++.

There is no "static constructor" feature in C++. On the other hand, there is no problem with assigning to static member variables in any of these languages. And finally, it isn't a good solution to your problem in any case!

Firstly, use std::vector consistently wherever you are tempted to call new[]. The whole point of std::vector is to make manually allocated arrays unnecessary.

To actually solve your problem, you need to put the shared data in a separate object, which then all the other objects hold a pointer to. The simplest way to do this in C++ so that the shared object is deleted when no longer needed is to use boost's shared_ptr, also known as std::tr1::shared_ptr in current compilers/libraries.

Or for amusement, you could implement the same thing yourself. Just put an integer counter in the shared class. Every time a new client takes a pointer to the shared object of that class, they increment the counter. And in their destructor (or when they clear the pointer) then decrement the counter first. When the counter falls to zero, delete the shared object.

class SharedThing
{
    int needed;

    // other data

public:
    SharedThing()
        : needed(1) { }

    SharedThing *share()
    {
        needed++;
        return this;
    }

    void release()
    {
        if (--needed == 0)
            delete this;
    }
};


SharedThing *a = new SharedThing();
SharedThing *b = a->share();
SharedThing *c = b->share();

// a, b, and c all point to the same object, 
// which now internally has a 'needed' count of 3

a->release(); // down to 2
b->release(); // down to 1
c->release(); // deleted

From the perspective of each local piece of code with a pointer to SharedThing, after calling release on it, you should assume it has been deleted.

The shared_ptr template class makes this facility reusable, so you don't have to manually call or implement share or release.

Update again

Based on the information you've added, it looks like the shared data has the same lifetime as an object of the class Pool. So make Pool the owner of it, responsible for allocating it (and setting it up) and also deleting it.

And I repeat, use std::vector<T>, not raw new T[] arrays.

Upvotes: 0

bill s
bill s

Reputation:

Without much experience with static members, here is how I modified my code. (The static members **a and n of Solution are initialized inside the Pool constructor)

class Solution
{
    double *x;
    double y;
    static double **a; // the same for all instances
    static int n; // the same for all instances
public:
    static void StaticSet(int,double**)
    Solution();
    ~Solution();
    double yFunc();
}
// assign static variables
Solution::StaticSet(int numX,double **coef)
{
    n=numX;
    // allocate memory for coefficients
    a = new double*[2];
    for(int i=0;i<2;i++) a[i] = new double[n];
    // assign coefficients
    for(int i=0;i<2;i++) 
        for(int j=0;j<n;j++)
            a[i][j] = coef[i][j];
}

// construct Solution
Solution::Solution()
{
    // generate a random solution in [-1,+1] range
    srand(time(0));
    for(int j=0;j<n;j++)
        x[j] = 2.0*(rand()/(double)RAND_MAX-0.5);
}

// construct Pool
Pool::Pool(int numSols, int numX, double **coef)
{
    poolSize = numSols;
    Solution::StaticSet(numX,coef);
    for(int i=0;i<poolSize;i++)
        sols.push_back(new Solution());
}

Is that correct?

Upvotes: 0

ralphtheninja
ralphtheninja

Reputation: 133008

I heard about static members, but as I understand, a user is not allowed to assign their values through an external function call.

Wrong.

class A
{
  public:
    static int x_;

    static void StaticSetX(int x) { x_ = x; }
    void NeedInstanceSetX(int x) { x_ = x; }
};

int main()
{
    // an instance of A can access both static methods and non static
    A a;
    a.NeedInstanceSetX(1);
    a.StaticSetX(2);

    A::NeedInstanceSetX(4); // Compiler error, method not static
    A::StaticSetX(3); // Ok, StaticSetX() is a static method

    return 0;
}

Upvotes: 0

anon
anon

Reputation:

Your understanding of statics appears to be a bit off. A static variable is simply one that is shared by all instances of the class. You can access it as you would any non-static variable, but you can also access it without a class instance.

The topic is perhaps a bit broad for a SO answer, and is best addressed by a good C++ text book - which one are you using? Perhaps if you tried to modify your code to use statics and then posted questions regarding the problems you were having, we could help more.

Upvotes: 1

Related Questions