How to implement a class counter in DLL?

2019-09-05 08:38发布

问题:

So far I have:

// TypeCounter.h

template <typename Base>
class Counter : public Base
{
protected:
    static int typeIndexCounter;
};

template <typename T, typename Base>
class Bridge : public Counter<Base>
{
public:
    virtual ~Bridge() {}

    virtual int GetTypeIndex() const
    {
        return TypeIndex();
    }

    static int TypeIndex()
    {
        static int typeIndex = typeIndexCounter++;
        return typeIndex;
    }
};

// static variable definition
template <typename Base>
int Counter<Base>::typeIndexCounter;

Use case is like:

class SBase
{ ... }
class S0 : public Bridge<S0, SBase>
{ ... }
class S1 : public Bridge<S1, SBase>
{ ... }
class S2 : public Bridge<S2, SBase>
{ ... }

So for each kind of Base class, I can count how many derives it has (if it has ever been instantiated, of course).

The problem is that, this Counter class is in a DLL.

If I have multiple users of this DLL, then each user would instantiate its own Bridge and Counter, then the TypeIndex() can be different among these users for the same T.

I tried to put __declspec(dllexport) before Counter and Bridge, but there are compile errors on the user side (the user side would change export to import, by defining a macro).
Despite the errors, it is also a wrong approach, since one should not export class template.

So I want to make sure that the TypeIndex() keep the same for certain T, even if there are many instantiations for the same T, how can I achieve this?

Thanks!

回答1:

Use a map to store information. This doesn't involve any inline code (except the CRTP mixin to register and do the counting), but it should avoid different parts going out of sync.

That said, when you write "users", you don't mean users in the sense of people that have a login, right? Reason is that this is always only a count within a single process, different processes by the same or different users don't have any effect on this.



回答2:

It is true that you cannot export a class template. This is even useless, since you have all the class template written in the header file, so no export is actually required to be able to use the code. Instead, you have to export the class instantiation, and later use it with the "extern" keyword:

This is the header file when compiling the library:

class SBase{ ... }

template class __declspec(dllexport) Bridge<S0, SBase>;
class S0 : public Bridge<S0, SBase>
{ ... }
template class __declspec(dllexport) Bridge<S1, SBase>;
class S1 : public Bridge<S1, SBase>
{ ... }
template class __declspec(dllexport) Bridge<S2, SBase>;
class S2 : public Bridge<S2, SBase>
{ ... }

And this is the header file when building the client code:

class SBase{ ... }

extern template class __declspec(dllimport) Bridge<S0, SBase>;
class S0 : public Bridge<S0, SBase>
{ ... }
extern template class __declspec(dllimport) Bridge<S1, SBase>;
class S1 : public Bridge<S1, SBase>
{ ... }
extern template class __declspec(dllimport) Bridge<S2, SBase>;
class S2 : public Bridge<S2, SBase>
{ ... }

So now, the linker will search for this classes through all your program, not only in your compiling module.

This should be typedefed, in order to get the different outputs using the same code:

#ifdef LIBRARY_BUILD
#define EXPORT __declspec(dllexport)
#define TEMPLATE_EXPORT
#else
#define EXPORT __declspec(dllimport)
#define TEMPLATE_EXPORT extern
#endif

class SBase{ ... }

TEMPLATE_EXPORT template class EXPORT Bridge<S0, SBase>;
class S0 : public Bridge<S0, SBase>
{ ... }
TEMPLATE_EXPORT template class EXPORT Bridge<S1, SBase>;
class S1 : public Bridge<S1, SBase>
{ ... }
TEMPLATE_EXPORT template class EXPORT Bridge<S2, SBase>;
class S2 : public Bridge<S2, SBase>
{ ... }

This have to be done in ALL template classes with static members, so all the template classes have got only ONE instantiation of the members, instead of one per compilation module.

You can have further information here: classes and static variables in shared libraries

EDIT: Re-reading your question, I would declare as extern your template base class (since it is the one with the static member), not the particular ones:

TEMPLATE_EXPORT template class EXPORT Counter<SBase>;

So now, you can have any SBase's derived class and all will be relying on the same Counter class, without the need to declare themselves as extern. Of course, if SBase is in some library, it should be declared also itself as extern (in the normal way, just __declspec(dllexport), since it is not a template).