• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

C++ Tutorial - A class to handle L: variables in C++

Messages
240
Country
switzerland
That's another tutorial commenting the GDI+ C++ sample I've posted a few days ago.

Some people had a look at the source code and already noticed it included some classes to handle L: variables, here's the explanation how they work. This is not by any means the only way it could be done, it's more like an example to have a chance to discuss how C++ and OOP techniques could be used to write code that will speed up development.

Here's the link to the project:

http://www.mediafire.com/download/zidu446ar6l2awz

The class is named CNamedVar and it's declared in the CSimulation.h file of the example, let's have a look at its source:

Code:
CNamedVar(const char* varname, const double start_value = 0, const double min_value = 1, const double max_value = 999999, const bool wrap = false, const double max_frames = 200, const double not_animate_if = 0)
    : mVarName(varname), mMinValue(min_value), mMaxValue(max_value), mWrap(wrap), mValue(start_value), mID(-1), mNotAnimateIf(not_animate_if), mMaxFrames(max_frames)
{
    mID = register_named_variable(mVarName);
    Set(start_value);
}

This is the class constructor, which is called automatically when a new variable of that class is instantiated. The constructor has many parameters, but only the first one is mandatory, which is a string containing the name of the variable. All other parameters are optional, which means if you don't use them, they will have default values. In order to keep this post shorter, we'll discuss the other parameters in a separate thread.

This means, in the simplest way, you could create a new L: variable this way:

Code:
CNamedVar MyVar = CNamedVar( "MyVar" );

The constructor will call the register_named_variable() from the Panels SDK for you, will store the ID as an internal member ( so you won't have to be concerned in your code ) and will call the Set() member function to initialize it to a default starting value. Since the start_value parameter is optional, we haven't use it, and has a default value of 0, the variable will be automatically set to 0.

Using default values and constructors is a good way to be absolutely *sure* your variables WILL be initialized, since many difficult to find bugs in C (where you could easily declare a variable without initializing it, pointing to random memory data) are usually related to having forgot to initialize a variable. C++ tries to help you by using constructors and initialization lists to reduce this risk.

A good place to declare this new variable could be in the CSimulation Singleton. Although the notation L: seems to suggest that L: variable are "Local", they are in effect Globals, because an airplane gauge declaring an L: variable, would expose it to the scenery engine on a global basis. Since they *are* globals by any means, we could put them in our "box of globals", which is the CSimulation Singleton.

A convenient way of doing it would be declaring the variable in the Singleton constructor declaration, and instancing it in the Singleton constructor initialization list, this way:

Code:
class CSimulation : public CSingleton<CSimulation>
{
    friend class CSingleton<CSimulation>;
public:
    void Start(void) { mStarted = true; };
    void Stop(void) { mStarted = false; };
    const bool IsStarted(void) { return mStarted; };

    void Update6hz(void);
    void Update1Sec(void);
    void Update4Sec(void);
    void UpdateFrame(void);

  CNamedVar MyVar;

private:
    CSimulation()
        : mStarted(false), MyVar( "MyVar" )
    {};

    ~CSimulation()
    {};

    bool mStarted;
};

I've removed the other variables used in the example to make this post shorter (you'll find them in the .ZIP file), and to make easier to see how the variable is declared. Declaration only is made in the Singleton constructor declaration, and initialization is made in the constructor initialization list, the one that starts after the : sign and before the {} brackets, which makes the constructor body.

To read/write the variable, we'll use the Get() and Set() member functions. The Get() function is very straightforward:

Code:
double Get(void)
{
    if (mID >= 0)
    {
        mValue = get_named_variable_value(mID);
    }
    return mValue;
}

It takes no parameters in input, and it will return the result as a double. L: variables are always doubles, so there's no need to use any kind of polymorphism here. The function checks the mID member to be sure the variable is valid and an ID has been assigned to it by FSX, and it will set the mValue member to the value returned by the Panels SDK function get_named_variable_value(), so now our internal member is holding the value as returned by FSX. Finally, it just returns the mValue member to the caller.

The Set() function is also very easy to understand:

Code:
void Set(const double val)
{
    if (mID >= 0)
    {
        mValue = val;
        set_named_variable_value(mID, (FLOAT64)mValue);
    }
}

It takes a double named "val" as input, and will not return anything. Again, we check if the mID is valid, and we'll both set the mValue member to the value of the "val" parameter, and then we call the set_named_variable_value() function of the Panels SDK to set the variable.

Since we put this L: variable inside our Singleton, which has been declared as a global pointer at the start of the GaugeDeclarations.cpp this way:

Code:
CSimulation* Sim = NULL;

And instanced in the module_init() function of the same file:

Code:
Sim = CSimulation::Instance();

We can now access to the Singleton using the Sim pointer, which is the "remote control" of our global Singleton object, which now CONTAINS our new L: variable.

Now that our variable has its place in the simulation, so let's do something with it, (it doesn't make much sense, but it would clear the concept), we'll change the variable value to a random factor every second, and we'll reset it to 0 when it reaches a certain value, pretending is some kind of a system simulation of sort, just for the example's sake.

So, in the Update1Sec() function, defined in the CSimulation.cpp file, we can add the following:

Code:
void CSimulation::Update1Sec(void)
{
    MyVar.Set( (double)rand() / RAND_MAX * 100.0);

    if( MyVar.Get() > 75 )
        MyVar.Set( 0 );
}
Very silly code, but it shows how to use the variable, we set it to a random value from 0 to 100, but we reset it to 0 if the value was higher than 75. This is happening every second.

Note that, we don't need to use the Sim-> notation to access the variable, because we ARE inside the Singleton class, so we can just use the variable name directly. The Sim-> notation will have to be used in case we'll need to access the variable from OUTSIDE the Singleton class (like in a gauge mouse handler routine, for example), and we can access it from anywhere, since we declared the Sim as a global pointer to the Singleton.

Now, we would probably want to display that value, so we go into the GDIPlus_Gauge.cpp file, in the DRAW cycle, doing this:

Code:
StringCbPrintf(mpszDest, mcbDest, L"LAT: %6.3f°\nLON: %6.3f°\nALT: %8.0f ft.\n\nHeading: %6.3f°\nMyVar: %.2f",
          Sim->PlaneLatitude.Get(),
          Sim->PlaneLongitude.Get(),
          Sim->PlaneAltitude.Get(),
          Sim->Heading.Get(),
          Sim->MyVar.Get()
          );

We just added MyVar to the list of variables to be printed. Here, we MUST access the variable through the Sim-> pointer, because we are in a gauge function, not in the Singleton class anymore.

A nice thing of using an OOP approach, is that you can be consistent and have an easy to understand code. Here, we have SOME variables that are internal of the FSX simulation ( the A: variables ), and one custom L: var we just made up. But regardless of what they are, you use it in the same way, accessing the Get() member function, that will do the different kind of interfacing with the sim, depending on the variable.

And, OOP would shield you from doing conceptual mistakes. For example, A: variables cannot be set through the panels SDK (there's no aircraft_varset() as a counterpart to the aircraft_varget() ), but they have key events instead that will act on their values. So, if you tried to do something like this:

Code:
Sim->PlaneLatitude.Set( 10 );

The C++ compiler won't let you, because we haven't defined a Set() member function for objects of the CFSVar, which is the type associated to A: variables, we discussed in the other tutorial. So, you don't risk writing something that doesn't make sense, because you can't even compile it so, your own *design* of the class can help you from doing mistakes.

This concludes the first part of the tutorial. In the next one, I'll discuss some extra features of the CNamedVar class, using the other optional parameters and how they could be used in a typical airplane project.
 
Last edited:
Thank you, Umberto! This is extremely valuable information. :cool:
 
I agree. A very good example, crisp and clearly explained, thank you.

Tom
 
Just saw this,

CNamedVar MyVar;

Doesn't this declaration means CNamedVar Class has only one existence? (sorry, I got no better way to describe it.)

For example, you cant do this

Code:
CNamedVar MyVar;
CNamedVar MyVar2;

or can you?

The whole idea of this is to reuse the "object" container, so I don't have to have create many classes (physically code) for each individual variables.

PS: My foundation of C++ is not that fantastic, I am learning as well. Bare with me if this is a stupid question. o_O
 
Last edited:
Doesn't this declaration means CNamedVar Class has only one existence? (sorry, I got no better way to describe it.)

No, that declaration means you have just declared the MyVar variable to be of the CNamedVar *kind*, it's not so different than saying that MyVar is an Int or a Double, just that a class usually has members, which can be either fundamental data types (int, double, float, etc.) or instances of other classes too. And they usually contains member functions too, which is executable code that is contained in the class.

For example, you cant do this

Code:
CNamedVar MyVar;
CNamedVar MyVar2;

or can you?

Sure you can and it's one of the most common forms...maybe it might been easier to understand if I explain it this way:

In the .H file:
Code:
class Employee
{
    string firstname;
    string surname;
    string address1;
    string address2;
    double salary;

    string GetFullName() { return firstname + " " + surname };
    string GetFullAddress() { return address1 + " " + address2 };

    Employee( string name, string sname, string addr1, string addr2, double s )
      : firstname( name ), surname( sname ), address1( addr1 ), address2( addr2 ), salary( s )
    {};
}

In the .CPP file:
Code:
Employee employee[2];

employee[0] = Employee( "John",  "Doe", "90125", "1st Elm St.", 60000.0 );
employee[1] = Employee( "Mary",  "Poppins", "90126", "2st Elm St.", 55000.0 );

for( int i = 0; i < 2; ++i )
   printf( "Employee %s lives at %s and makes %f at year", employee[i].GetFullName(), employee[i].GetFullAddress(), employee[i].salary );

Silly example ( I wrote it from memory, not sure it will compile without errors...), just to point out that is very common to have many instances of the same class.

Now, I intentionally put the printf outside of the code in this first example, to show how you can encapsulate further, this way:

In the .H file:
Code:
class Employee
{
    string firstname;
    string surname;
    string address1;
    string address2;
    double salary;

    string GetFullName() { return firstname + " " + surname };
    string GetFullAddress() { return address1 + " " + address2 };

    void Print( void );

    Employee( string name, string sname, string addr1, string addr2, double s )
      : firstname( name ), surname( sname ), address1( addr1 ), address2( addr2 ), salary( s )
    {};
}

In the .CPP file:
Code:
void Employee::Print( void )
{
  printf( "Employee %s lives at %s and makes %f at year", GetFullName(), GetFullAddress(), salary );
}

Employee employee[2];

employee[0] = Employee( "John",  "Doe", "90125", "1st Elm St.", 60000.0 );
employee[1] = Employee( "Mary",  "Poppins", "90126", "2st Elm St.", 55000.0 );

for( int i = 0; i < 2; ++i )
   employee[i].Print();

See how much cleaner the 2nd version looks like, because I added the Print as a member function of the class ? And, by declaring it outside the class declaration itself, in the .CPP file, without the inline keyword, I made sure that no code will be duplicated, there will be only one "Print" function in the compiled code, regardless how many instances of the Employee we have.

The whole idea of this is to reuse the "object" container, so I don't have to have create many classes (physically code) for each individual variables.

You don't create many classes, you create many INSTANCES of the same class. Actual code ( with "code" meaning the code for member functions ) will not be duplicated, except when a specific member function is part of the class declaration in the .H file, or when it's in the .CPP file, but declared with the "inline" keyword.

Data member will of course have to be duplicated, you *want* to store your data, and you needed to store the same data, it would take the same space, even without OOP, the point of having data+functions close together, is what is called ENCAPSULATION, which usually allows for better managed code.

But again, C++ gives you almost unlimited control and, each data member and/or each member function, can be declared as "static", which means all the instances of that class will share that member. So you can have members (both data and functions) that are shared between instances, and members that are unique to each instance, in the same class.
 
Last edited:
Just a big thanks to Umberto tutorial, I have learned a lot about OOP here.

As I simply love automation and hate using more than one liners, here is my more or less "final" solution to this class thing.

Header Declaration

Code:
class LVars;

static std::vector<LVars*> pointers;
static volatile bool vecpointersflag;

class LVars
{
public:
    int mID;
    double mValue;
    bool CallbackAdded;

    void AddInstance(LVars* instance)
    {
        pointers.push_back(instance);
    }

    inline void Update()
    {
        mValue = get_named_variable_value(mID);
    };

    inline double Get()
    {
        if(CallbackAdded == false && !vecpointersflag)
        {
            vecpointersflag = true;

            this->AddInstance(this);
            CallbackAdded = true;

            vecpointersflag = false;
        }

        return mValue;
    };

    inline void Set(double val)
    {
        mValue = val;
        set_named_variable_value ( mID, (FLOAT64)mValue ) ;
    };

    LVars(const char* varname) : mNameVar(varname), mValue(0), mID(0), CallbackAdded(false)
    {
        register_named_variable(mNameVar);
        mID = check_named_variable(mNameVar);
    };


private:
    const char* mNameVar;

};

and the magic one liner code still in the header:

Code:
//LVars Declaration
static LVars TEST_VAR = LVars("TEST_VAR");

and in the update function:

Code:
if(!vecpointersflag)
{
vecpointersflag = true;

for(unsigned i = 0; i < pointers.size(); i++)
    pointers[i]->Update();

vecpointersflag = false;
}

and to simply access it:
Code:
TEST_VAR.Get();

This eliminates of needing to register update "callbacks" for every single LVar which can really add up to tedious work when you have 40+ LVars.

Of course, you can create containers for different refresh rates. I will leave those who wants to use this to explore on their own.
 
Last edited:
The use of vectors is not thread-safe.

Thanks, just realised that. Added a simple lock guard when pushing back the vector.

Edit: on second thought, I remembered to synchronised the two threads (drawing and vars updating from simconnect and lvars) - one stops, the other starts, vice versa. So there isn't an issue there.
 
Last edited:
I merely mentioned it to ensure others who are learning from this thread understand the use of vectors is not thread safe and requires special handling.
 
This eliminates of needing to register update "callbacks" for every single LVar which can really add up to tedious work when you have 40+ LVars.

I don't understand why you need to register those "callbacks".

I notice in your example you are building a vector of pointers in Update() function, but you are retrieving the vars values with Get(), which means you actually need to call every var object individually to obtain its data.

Wouldn't be much simpler to define the class like :

Code:
class LVars
{
public:

    LVars(const char* varname) : mNameVar(varname), mValue(0), idID(0)
    {
        register_named_variable(mNameVar);
        idID = check_named_variable(mNameVar);
    };

    double Get()
    {
        mValue = get_named_variable_value(idID);
        return mValue;
    };

    void Set(FLOAT64 val)
    {
        set_named_variable_value ( idID, val ) ;
    };

private:
    const char* mNameVar;
    ID idID;
    double mValue;
};

Then the init step:

Code:
LVars TEST_VAR = LVars("TEST_VAR");
LVars OTHER_VAR = LVars("Other Var");

And to set/retrieve values:

Code:
TEST_VAR.Set(150.34);
double dValue = TEST_VAR.Get();  // *NOT* TEST_VAR->Get() as it is not a pointer but the object itself.
OTHER_VAR.Set(1);
etc.


Tom
 
I don't understand why you need to register those "callbacks".

I notice in your example you are building a vector of pointers in Update() function, but you are retrieving the vars values with Get(), which means you actually need to call every var object individually to obtain its data.

Wouldn't be much simpler to define the class like :

Code:
class LVars
{
public:

    LVars(const char* varname) : mNameVar(varname), mValue(0), idID(0)
    {
        register_named_variable(mNameVar);
        idID = check_named_variable(mNameVar);
    };

    double Get()
    {
        mValue = get_named_variable_value(idID);
        return mValue;
    };

    void Set(FLOAT64 val)
    {
        set_named_variable_value ( idID, val ) ;
    };

private:
    const char* mNameVar;
    ID idID;
    double mValue;
};

Then the init step:

Code:
LVars TEST_VAR = LVars("TEST_VAR");
LVars OTHER_VAR = LVars("Other Var");

And to set/retrieve values:

Code:
TEST_VAR.Set(150.34);
double dValue = TEST_VAR.Get();  // *NOT* TEST_VAR->Get() as it is not a pointer but the object itself.
OTHER_VAR.Set(1);
etc.


Tom

Well, in the drawing code I have, the variables get is called more than once.

It would be more efficient to get the update once and refresh it at the suspend of the drawing thread.

I have not measured the cost performance of the get_named_variable , but its just a habit that I picked up to avoid calling stuff too many times. (esp APIs when I don't document the performance)

The -> part is from my old code where the object was a pointer. I have corrected it in the post.
 
Last edited:
Well, in the drawing code I have, the variables get is called more than once.

That's precisely the reason why I had an CFSVar::Update() function and a CFSVar::Get() function in my implementation! You call Update() when you want to peek the current value from FSX, and you call Get() when you are referencing the variable again and you don't need to refresh it against the sim.

I'm not sure I understand what advantages you have with this approach. If you wanted to use a vector of variables, there wasn't any need to use the vector inside the class that does the interface to FSX. In fact, it makes the program less clear about what it does and less reusable.

I think it's best to have the part that reads a variable from FSX to be separated from its actual implementation together with other variables, that will change depending on your project. What changes between projects, is the Singleton implementation so, if you wanted to use a vector of objects to store variables, you could have declared them as a vector of CFSvars inside the Singleton, and eventually make a loop or (even better), an ITERATOR, to Update them in the various update callbacks. You don't have to register "register update callbacks for every single LVar", you only need ONE callback for each refresh class you need to use. This one MIGHT contain a cycle that iterates over a vector of variables to call all their Update() functions.

I don't think threading is an issue here, unless you create a new thread explicitly, either by calling the CreateThread() Windows API function, or if you get a thread created for you, for example when setting up a Windows timer that will callback your code, in THAT case you'll have to be careful of threading issues, and use safeties like mutex or other kind of guards IF use those vectors from the other thread.
 
Well, in the drawing code I have, the variables get is called more than once.

That's precisely the reason why I had an CFSVar::Update() function and a CFSVar::Get() function in my implementation! You call Update() when you want to peek the current value from FSX, and you call Get() when you are referencing the variable again and you don't need to refresh it against the sim.

I'm not sure I understand what advantages you have with this approach. If you wanted to use a vector of variables, there wasn't any need to use the vector inside the class that does the interface to FSX. In fact, it makes the program less clear about what it does and less reusable.

I think it's best to have the part that reads a variable from FSX to be separated from its actual implementation together with other variables, that will change depending on your project. What changes between projects, is the Singleton implementation so, if you wanted to use a vector of objects to store variables, you could have declared them as a vector of CFSvars inside the Singleton, and eventually make a loop or (even better), an ITERATOR, to Update them in the various update callbacks. You don't have to register "register update callbacks for every single LVar", you only need ONE callback for each refresh class you need to use. This one MIGHT contain a cycle that iterates over a vector of variables to call all their Update() functions.

I don't think threading is an issue here, unless you create a new thread explicitly, either by calling the CreateThread() Windows API function, or if you end up with a new thread created for you, for example when setting up a Windows timer that will callback your code, in THAT case you'll have to be careful of threading issues, and use safeties like mutex or other kind of guards IF use those vectors from the other thread.
 
I'm not sure I understand what advantages you have with this approach. If you wanted to use a vector of variables, there wasn't any need to use the vector inside the class that does the interface to FSX. In fact, it makes the program less clear about what it does and less reusable.

I think it's best to have the part that reads a variable from FSX to be separated from its actual implementation together with other variables, that will change depending on your project. What changes between projects, is the Singleton implementation so, if you wanted to use a vector of objects to store variables, you could have declared them as a vector of CFSvars inside the Singleton, and eventually make a loop or (even better), an ITERATOR, to Update them in the various update callbacks. You don't have to register "register update callbacks for every single LVar", you only need ONE callback for each refresh class you need to use. This one MIGHT contain a cycle that iterates over a vector of variables to call all their Update() functions.

The Vector is simply a vector of pointers. The reason why I used vector is to put them in a "list", as I said to remove the need to do this "XXX.Update();" for every LVar.

I am not quite sure how to do what you are saying here (going to read up on this after I type this), but I am pretty sure doing that removes the one line "declaration" advantage. Especially coming from XML where I used to be able to simply use the LVar simply just by typing the name of the LVar, I find this easier to get used to, rather than many tedious lines to get what I needed.
 
The Vector is simply a vector of pointers. The reason why I used vector is to put them in a "list", as I said to remove the need to do this "XXX.Update();" for every LVar.

I'm not questioning the use of a vector, and being able to iterate over a vector to call their Update() function it's an obvious reason to use it, although it won't improve performances at all, it will just result in a shorter code. I would have included the use of a vector to do all the updates, but didn't wanted to add another things to discuss in this tutorial, that might have confused things a bit.

What I'm questioning, is the usage of the vector in the same class that also interfaces with the FSX to call the SDK functions to update it. That looks like a messy design to me, because the calls to the panels SDK functions are oriented to a single var, so it make sense to wrap them as an object. The vector of pointers LOGICALLY relates more to the concept of "box of variables" that I've explained in another tutorial, so the natural place for it would have been inside the Singleton that holds all variables (grouped into containers or not, it doesn't matter), not in the class that does the access through the SDK Panels API. Again, it's a LOGICAL thing, I'm not questioning if it's working or not, it just looks messy, and to strongly coupled to a specific project = less reusable.

I am not quite sure how to do what you are saying here (going to read up on this after I type this), but I am pretty sure doing that removes the one line "declaration" advantage. Especially coming from XML where I used to be able to simply use the LVar simply just by typing the name of the LVar, I find this easier to get used to, rather than many tedious lines to get what I needed.

I think you are really missing the forest for the tree here...being able to use and declare in one line is surely not what makes or breaks the cleanliness/readability/reusability of your code, and I don't fully understand what you are referring to with "many tedious lines". There are many ways to declare/use variables, and C++ gives you many alternate ways to do things, the trick is being able to select the one that fits your project.

If you could do a real world example of a code that is hard to read and with "many tedious lines", maybe I could suggest you a better way to express it, like using templates, different data structures like maps, etc.
 
I'm not questioning the use of a vector, and being able to iterate over a vector to call their Update() function it's an obvious reason to use it, although it won't improve performances at all, it will just result in a shorter code. I would have included the use of a vector to do all the updates, but didn't wanted to add another things to discuss in this tutorial, that might have confused things a bit.

What I'm questioning, is the usage of the vector in the same class that also interfaces with the FSX to call the SDK functions to update it. That looks like a messy design to me, because the calls to the panels SDK functions are oriented to a single var, so it make sense to wrap them as an object. The vector of pointers LOGICALLY relates more to the concept of "box of variables" that I've explained in another tutorial, so the natural place for it would have been inside the Singleton that holds all variables (grouped into containers or not, it doesn't matter), not in the class that does the access through the SDK Panels API. Again, it's a LOGICAL thing, I'm not questioning if it's working or not, it just looks messy, and to strongly coupled to a specific project = less reusable.



I think you are really missing the forest for the tree here...being able to use and declare in one line is surely not what makes or breaks the cleanliness/readability/reusability of your code, and I don't fully understand what you are referring to with "many tedious lines". There are many ways to declare/use variables, and C++ gives you many alternate ways to do things, the trick is being able to select the one that fits your project.

If you could do a real world example of a code that is hard to read and with "many tedious lines", maybe I could suggest you a better way to express it, like using templates, different data structures like maps, etc.

I think I understand now where you are coming from this, yes its messy and there probably is a better way of doing the structure, but I don't have time to restructure my whole current code for this. The next "project" I will start from a fresh with a "better" structure, but not now.

That may be true for "missing the forest for the tree". Previously, before this "class" tutorial, I had to manually add mID and value for every variable, and then add even more lines for register,check,get. Using your method in the first post, it reduces the no of lines even further to declaring the "CNamedVar" and then in the class initialization add another "MyVar( "MyVar" )". Now, here's the "tricky" part, I have a excel file contains the list of all my LVars, I would like to be able to simply copy and paste the list of LVars to be able to use those LVars. Using my method, I am able to first duplicate many lines of the "one liner", then using vertical copy and paste in Notepad++, I simply paste in the List. I doubt I will be able to accomplish the same using the original method in the first post for such "mass" production.

If you could offer some tips on how to achieve the "mass" production, I would be glad to take it on board and try it in the next "production".
 
Now, here's the "tricky" part, I have a excel file contains the list of all my LVars, I would like to be able to simply copy and paste the list of LVars to be able to use those LVars. Using my method, I am able to first duplicate many lines of the "one liner", then using vertical copy and paste in Notepad++, I simply paste in the List. I doubt I will be able to accomplish the same using the original method in the first post for such "mass" production.

If you could offer some tips on how to achieve the "mass" production, I would be glad to take it on board and try it in the next "production".

I second what Umberto has stated above.

In your Excel's example, the vector of pointers fits very well.

You declare the vector (could be a a member of the Singleton ), for example:

Code:
vector <LVars*> vVars;

Also you build a table with your lvar names, copied and pasted from Excel:

Code:
PCSTRINGZ VarNames[] =
{
  "My Lvar1",
  "Second Var",
  "Etc",
// ---keep pasting from Excel here, or add a new var name manually
};

Then, if you prefer to use the actual var names as an easy reference, declare a list of enum names, like:

Code:
enum eVars
{
     My_LVar,
     Second_Var,
     Etc,
    // again, paste from Excel or add manually
};

And finally, initialize the var instances with something like:

Code:
    int i;
    LVars* pVars;
    for( i=0; i < LENGTHOF(VarNames);i++ )
    {
        pVars = new LVars(VarNames[i]);
        vVars.push_back(pVars);
     
       vVars[i]->Update() // Update method could be placed here or elsewhere
    }

Now, to simply reference a var with a proper identifier:

Code:
vVars[My_LVar]->Get();
vVars[Etc]->Set(value);

As you can see, very simple and clean.

Tom
 
If you could offer some tips on how to achieve the "mass" production, I would be glad to take it on board and try it in the next "production".

The first thing that comes in mind, if you have an external file with a huge list of variables, could be reading THAT file, perhaps in an easier to parse form (like .CSV) in the Singleton constructor body, and instance those classes using the name of the variable read from the file directly...

Note that, while this will make your startup code incredibly short, and would make your update() code very short too, it won't make it perform ANY faster compared to just having everything declared explicitly, but it will also make your code difficult to read.

Yes, of course it's very tempting to be able to write something like this:

Code:
for( size_t i; i < myVector.size(); ++i )
  {
  myVector[i]->Update();
  }

But if you need to access a single variable, like this

Code:
double x = myVector[132]->Update();

How you would know that pointer # 132 was supposed to point to your "MyVarXXX" L: variable ?

An std::map data structure MIGHT help you here, because a Map uses a key, element structure. Assuming you declared the std::map as to use a string as a key ( the name of the variable ) and the pointer to the variable as an element, you might do something like this:

In the CSimulation.H Singleton declaration
Code:
#include <map>
//
//
  std::map <std::string, CNamedVar> LVars;

In the Singleton constructor body, same file:
Code:
private:
    CSimulation()
        : mStarted(false)
    {
        LVars["MyVar1"] = CNamedVar("MyVar1", 4, 2, 10, true );
        LVars["MyVar2"] = CNamedVar("MyVar2", 3, 1, 9, true);
    };

    ~CSimulation()
    {};

    bool mStarted;
};

To access the variables, from outside the class, for example while drawing the gauge in GDIPlus_Gauge.cpp (I'm referring to my sample, for clarity )

Code:
  Sim->LVars["MyVar1"].Get();
  Sim->LVars["MyVar2"].Get();

These are associative arrays, a common feature in other languages, but something that can be done in C++ using the std::map data structure. This approach will save you from having to figure it out which variable in the potentially long list of pointers was supposed to be what.

Note that, the name of the map Key doesn't HAVE to be the same of the L: variable! You can name it as you want, but of course you must be careful with it, because it might be easy to be confused, especially when reading the code years from now...
 
Last edited:
Now, a bit of C++ magic...assuming you used an std::map to store your CNamedVars L: variables, now that you saw how to access them "by name" using the associative array feature offered by std::map, you can ALSO update them all in one go, using an ITERATOR:

Code:
    for (std::map <std::string, CNamedVar>::iterator i = LVars.begin(); i != LVars.end(); ++i )
        i->second.Get();

This line will use an ::iterator i, which is a pointer to the currently cycled element of the map. Since it's pointing to an std::map, you'll get the following two elements:

i->first
this will point to the std::map KEY value, which in our case was a string containing the name we associated to the variable

i->second

will point to the actual value, a CNamedVar in our case, so we can just use the member functions defined in our class, like .Get().
 
Back
Top