0

I am a bit unsure if my title for this question is readable, but I don't know how else to put it.

So I have a class, let's call it MyClass declared as:

template <class T>
class MyClass
{
public:
  ...
  std::vector<T> vecData;
  ...
  void LoadData() {/* Loads data from database into vecData collection */}
}

A bit simplified, the code is used as:

MyClass<Cat> catInstance1;
catInstance1.LoadData();
....
MyClass<Cat> catInstance2;
catInstance2.LoadData();
...
MyClass<Dog> dogInstance1;
dogInstance1.LoadData();
...
MyClass<Dog> dogInstance2;
dogInstance2.LoadData();
...

However, now I would like the vecData collection to be static, because that vecData gets filled with objects, and it is currently done for each instance (even of the same template type), when in fact it would be perfect if I only had to load data once for every template type T. Meaning that catInstance1 would load data from database when calling LoadData(), but when catInstance2 calls the same function it should be able to use the data already loaded in by catInstance1. The same then applies for dogInstance1 and dogInstance2.

So I want the collection vecData to be static within every template type that the class is instantiated with (Cat or Dog in this example). How can one do it in C++? I have somewhat recently started to create own template classes in C++ so I could need a bit of help on this particular case.

Thanks!

Edit: Fixed code above as per comment from jarod42

10100111001
  • 735
  • 15
  • 31
  • Why is it related to MFC? – Petar Petrovic May 05 '17 at 07:55
  • @PetarPetrovic It's probably not...I'm just using MFC, so it is the Microsoft Compiler Visual Studio compiler is perhaps what I meant to show. In case something is handled differently somehow with regard to templates in that respect. MFC was perhaps not the correct word for it though. I can remove it. – 10100111001 May 05 '17 at 07:58
  • [OT]: `auto catInstance1 = new MyClass();` should probably be `MyClass catInstance1;`. (else you have to use `->` and `delete` the pointer later). – Jarod42 May 05 '17 at 08:40
  • @Jarod42 Yes, true! I was just typing from memory so I missed that. Thanks! – 10100111001 May 05 '17 at 08:53
  • Possible duplicate of [Can template classes have static members in C++](http://stackoverflow.com/questions/5827903/can-template-classes-have-static-members-in-c) – bolov May 05 '17 at 09:03
  • @bolov Oh yes good link! Only it does only address a float, and I saw such posts before (using primitive data types), but I couldn't find an example for collection and I couldn't get it to compile. But now I have solved it and provided the answer below. – 10100111001 May 05 '17 at 09:11

4 Answers4

0

You just need to declare the member as static. A separate instance of it will exist for each parametrization of the template.

See Can template classes have static members in C++ for more details.

Community
  • 1
  • 1
Horia Coman
  • 8,681
  • 2
  • 23
  • 25
  • I have tried to do so but then VS complains about missing external symbols when I make a function static that uses the vector. For instance a function: void CopyData(std::vector& tmpVec) { dstVec = vecData; } I get error of unresolved external symbols because for variants of the CopyData() method when compiled with T=Cat and T=Dog – 10100111001 May 05 '17 at 08:04
0

You can do smth like this:

class animal {
   ...
}

class cat : public animal{
  ...
}

class dog : public animal {
   ...
}

class Base {
protected:
  static std::vector<animal*> vectData;
}
std::vector<animal*> Base::vectData;

template<clas T>
class MyClass : public Base {
  void LoadData();
}
Andrew Kashpur
  • 736
  • 5
  • 13
  • Hmmm....I suppose that could work. But I think I managed to solve it by myself. I shall write an answer here now as to what I had to do. – 10100111001 May 05 '17 at 08:56
0

Thanks everyone for your input, I think I managed to solve it on my own! Here is what had to be done.

I had this class:

template <class T>
class MyClass
{
public:
  ...
  std::vector<T> vecData;
  ...
  void LoadData() {/* Loads data from database into vecData collection */}
};

The problem was I needed to provide a definition of the static vecData collection still in the header file but outside the MyClass{} scope like this:

Simply below the code above I added:

template <typename T>
std::vector<T> MyClass<T>::vecData = {};

This compiled and gave me the results I was looking for; static collection for each template type that the MyClass gets instantiated with. Great!

10100111001
  • 735
  • 15
  • 31
0

Consider the behaviour of a static member in templates

In a templated class, a static member is instantiated for each instantiated fully deduced template, i.e. they all share the single instance of data.

Consequently, declaring the vector static is enough.

BUT be aware

...that even though the data is shared, all LoadData()-calls would reload or append new data!

See the extensive example below!

#include <iostream>
#include <vector> 
#include <atomic>
#include <mutex>

template <typename T>
// Simple creator class providing us data
class Creator {    
    public:
        static T Create() {
            return T();
        }
};

template <>
// Simple creator class providing us data for INTEGER, just counting up from 0 on each call.
class Creator<int> {
        static int mCounter;

    public:
        static int Create() {
            return mCounter++;
        }
};

// Don't forget to initialize.
int Creator<int>::mCounter = 0;

template <typename T>
// Loader class encapsulating thread-synchronization, "already-loaded"-handling and effective data loading mechanisms...
// Here: Using the Creator<T>
class Loader {
    public:
        Loader()
            : mLoaded(false),
              mData() {
        }

        bool LoadData() {
            // Deactivate check, to allow confirming duplicate addition of data from two equally-typed containers! EXAMPLE ONLY
            // Uncomment, to have the "load only once" functionality.
            // if(mLoaded.load())
            //      return true;

            std::lock_guard<std::mutex> guard(mLoadMutex);
            bool loaded = LoadDataImpl();
            if(loaded)
                mLoaded.store(loaded);

            return loaded;
        }

        const std::vector<T>& ConstData() const { return mData; }

    private:
        bool LoadDataImpl() {
            // Actual data loading. Return true on success. False otherwise.   
            try {
                // Code...
                for(int i=0; i<10; ++i) {
                    mData.push_back(Creator<T>::Create());
                }

                return true;
            } catch(...) { // Actually don't use the ... but specific types.

                return false;
            }
        }

        std::mutex        mLoadMutex;
        std::atomic<bool> mLoaded;
        std::vector<T>    mData;
};

template <typename T> 
// The actual container, using the STATIC loader.
// The Loader<T>-static instance is shared among all Container<T>'s!
class Container {
    static Loader<T> mLoader;

    public:
        bool LoadData() { return mLoader.LoadData(); }
        const std::vector<T>& Data() const { return mLoader.ConstData(); }
};

// Don't forget to initialize...
template <typename T>
Loader<T> Container<T>::mLoader = {};

// Example struct
struct Whatever {
    int whateverValue;    
};

int main() {
    Container<int>      intContainer;
    Container<int>      intContainer2;
    Container<bool>     boolContainer;
    Container<Whatever> whateverContainer;

    // Load data 0..10 into buffer
    if(!intContainer.LoadData()) {
        std::cout << "Cannot load data of container 1.\n";
    } else {
        std::cout << "Got C1 data.\n";
    }

    // Load data 11..19 into buffer, IF we have the above check for mLoaded commented.
    // Otherwise, nothing will happen here!
    if(!intContainer2.LoadData()) {
        std::cout << "Cannot load data of container 2.\n";
    } else {
        std::cout << "Got C2 data.\n";
    }

    // 
    // If we commented the mLoaded-precondition, we will get a print of 0..19 in the console.
    // Otherwise 0..9
    //
    for(const int& v : intContainer2.Data()) {
        std::cout << v << "\n";
    }

    std::cout << std::endl;
}

I have created a small Demo, which:

  • Declares a Container
  • Using a Loader (which is shared among all COntainer's) as it is static.
  • The Loader uses a Creator as a dummy-databackend, counting 0 up on each call for int-types

THe important part here is, that the Loader provides locking for multithreaded access and an atomic "mLoaded", which checks if data was already loaded beforehand.

Since the additional dataloading logic would make the container itself clumsy, I encapsulated it in a Loader and have the beautiful one-liner static Loader mLoader.

Just read through the code and the comments in it and it should become appearent!

MABVT
  • 1,350
  • 10
  • 17
  • Hmmm...I managed to solve it, se my answer here. I added an IsLoaded() and SetLoaded() function and when I debug the code it seems to be working. Since my m_isLoaded flag gets set to true (also static for all instances of the same template type) it does not load data again from database and the data is already available in the collection from first call to LoadData() (from an instance of the same template type). – 10100111001 May 05 '17 at 09:16
  • I can try to recreate the errors, but you should. I am at work so I can't really chat right now but you should be able to create an example from my sample code perhaps. But I'll try to recreate the error messages later if I manage to. Basically, before I added that definition outside the MyClass{} scope, the linker could not find vedData and vecData because there was no definition. But as soon as I added the parts in the answer I wrote here the compiler created a static vecData for MyClass and vecData for MyClass as I understand it. – 10100111001 May 05 '17 at 10:09
  • @10100111001 dont mind I mixed up the question. If your solution works it is fine :). – MABVT May 05 '17 at 11:09
  • Aaah I see, okay :) – 10100111001 May 05 '17 at 11:19