static members of template classes optimized away?

M

Markus Henschel

Hello,

this is a test case of something I just can't explain myself:

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <list>
typedef void (*registerfunc)();

class CMgr
{
public:
static std::list<registerfunc> & GetRegisterList()
{
static std::list<registerfunc> registerlist;
return registerlist;
}

static bool Register(registerfunc func)
{
GetRegisterList().push_back(func);
return true;
}

};

template< class T> class THelper
{
public:
//virtual bool GetDummy() { return ms_bDummy; }

private:
static bool ms_bDummy;
};

template <class T>
bool THelper<T>::ms_bDummy=CMgr::Register(T::Register);


class CTest : public THelper<CTest>
{
public:
static void Register()
{
printf("Registering CTest\n");
}
};

int main(const char * args[], int iNumArgs)
{
printf("There are %d functions registered.\n",
CMgr::GetRegisterList().size());

return 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7

I just want a list of functions be generated when the program starts
up. The output of the program is the following:

Optimizations turned off:
"There are 0 functions registered."

Optimizations turned off and "GetDummy()" uncommented in THelper:
"There are 1 functions registered."

Leaving "GetDummy()" uncommented and turing optimizations on:
"There are 0 functions registered."

Instanciating CTest and calling GetDummy() from main():
"There are 1 functions registered."

I cannot explain this to me. When is the compiler optimizing away the
static member and not performing initialization?

Thanks a lot.

Markus
 
M

Maxim Yegorushkin

Markus said:
Hello,

this is a test case of something I just can't explain myself:

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <list>
typedef void (*registerfunc)();

class CMgr
{
public:
static std::list<registerfunc> & GetRegisterList()
{
static std::list<registerfunc> registerlist;
return registerlist;
}

static bool Register(registerfunc func)
{
GetRegisterList().push_back(func);
return true;
}

};

template< class T> class THelper
{
public:
//virtual bool GetDummy() { return ms_bDummy; }

private:
static bool ms_bDummy;
};

template <class T>
bool THelper<T>::ms_bDummy=CMgr::Register(T::Register);


class CTest : public THelper<CTest>
{
public:
static void Register()
{
printf("Registering CTest\n");
}
};

int main(const char * args[], int iNumArgs)
{
printf("There are %d functions registered.\n",
CMgr::GetRegisterList().size());

return 0;
}

Members of templates are instantiated on demand. As long as nothing in
the code accesses THelper<T>::ms_bDummy it does not get instantiated.
 
M

Markus Henschel

Right. When I uncomment the virtual function accessing ms_bDummy the
static member is initialized. But optimizations make the compiler
remove GetDummy because it is never called and this in turn removes the
static member. But shouldn't the compiler know that the function that
initializes m_bDummy is changing other variables that ARE accessed?

I had this idea of executing initialization time code from an article
about a property system. I checked it again and the only difference to
my code is that I use a bool as static member but the article used a
property struct. So I made some changes to the example code:

////////////////////////////////
template< class T> class THelper
{
public:
virtual bool GetDummy() { return ms_Dummy.m_bTest; }


static CDummy ms_Dummy;
};

template <class T>
CDummy THelper<T>::ms_Dummy=CMgr::Register(T::Register);


class CTest : public THelper<CTest>
{
public:
static void Register()
{
printf("Registering CTest\n");
}
};
//////////////////////////////////

This way the output is always: "There are 1 functions registerd." Can I
rely on this or is this just because the compiler isn't smart enough to
remove the GetDummy() member function?
 
M

Maxim Yegorushkin

Markus said:
Right. When I uncomment the virtual function accessing ms_bDummy the
static member is initialized. But optimizations make the compiler
remove GetDummy because it is never called and this in turn removes the
static member. But shouldn't the compiler know that the function that
initializes m_bDummy is changing other variables that ARE accessed?

I had this idea of executing initialization time code from an article
about a property system. I checked it again and the only difference to
my code is that I use a bool as static member but the article used a
property struct. So I made some changes to the example code:
[]

This way the output is always: "There are 1 functions registerd." Can I
rely on this or is this just because the compiler isn't smart enough to
remove the GetDummy() member function?

You can make your code more reliable by simplifying it.

First, I got rid of std::list<> and replaced it with a custom list
which only requires static initialization and thus is not prone to the
order of dynamic initialization between modules problem. You can
declare an extern object of this class in a header and safely use it
from constructors of global objects. Also, the list is intrusive, so
that it does not require dynamic allocations.

Second, I add elements into the list by declaring global (or namespace
scope) objects of a class. Its constructor adds it into the list and
this can not be optimized out.

struct registrations
{
struct node
{
void(*fn)();
node* next;
};

void push_front(node* n)
{
n->next = head_;
head_ = n;
}

void invoke_all()
{
for(node* n = head_; n; n = n->next)
n->fn();
}

node* head_;
} l = {};

template<void(*FN)()>
struct do_register : registrations::node
{
do_register()
{
this->fn = FN;
l.push_front(this);
}
};

struct some
{
static void register_fn() { printf("%s\n", __PRETTY_FUNCTION__); }
};

do_register<some::register_fn> const register_some;

int main()
{
l.invoke_all();
}
 
M

Markus Henschel

Thanks. So it is not possible to somehow force the initialization of
static members that are not used? It is just out if interest as your
solution of manually registering classes by filling a header file is
just slightly more work but offers more flexibility.
 
M

Maxim Yegorushkin

Markus said:
Thanks. So it is not possible to somehow force the initialization of
static members that are not used?

Just take its address.

class CTest : public THelper<CTest>
{
public:
static void Register()
{
void* p = &THelper<CTest>::ms_Dummy; (void)p;
printf("Registering CTest\n");
}
};
 
B

benben

Markus said:
Hello,

this is a test case of something I just can't explain myself:

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <list>
typedef void (*registerfunc)();

class CMgr
{
public:
static std::list<registerfunc> & GetRegisterList()
{
static std::list<registerfunc> registerlist;
return registerlist;
}

static bool Register(registerfunc func)
{
GetRegisterList().push_back(func);
return true;
}

};

template< class T> class THelper
{
public:
//virtual bool GetDummy() { return ms_bDummy; }

private:
static bool ms_bDummy;
};

template <class T>
bool THelper<T>::ms_bDummy=CMgr::Register(T::Register);


class CTest : public THelper<CTest>
{
public:
static void Register()
{
printf("Registering CTest\n");
}
};

int main(const char * args[], int iNumArgs)
{
printf("There are %d functions registered.\n",
CMgr::GetRegisterList().size());

return 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7

I just want a list of functions be generated when the program starts
up. The output of the program is the following:

Optimizations turned off:
"There are 0 functions registered."

Ok. CTest forces THelper<CTest> to instantiate. But non of the member of
Optimizations turned off and "GetDummy()" uncommented in THelper:
"There are 1 functions registered."

Now CTest again forces THelper<CTest> to instantiate. CTest also opens a
chance for other types which inherits CTest to override the virtual
function THelper<CTest>::GetDummy, and that forces
THelper<CTest>::GetDummy to instantiate, which in turn instantiates
THelper said:
Leaving "GetDummy()" uncommented and turing optimizations on:
"There are 0 functions registered."

Now the compiler performs whole program optimization and finds out that
the virtual function is never ever used, so optimized it off, taking
ms_bDummy down with it.

The compiler is gonna opt it away even if you explicitly instantiate it
because the optimization happens after all template instantiation. :)
Instanciating CTest and calling GetDummy() from main():
"There are 1 functions registered."

Well if you use GetDummy() then the optimizer of course can't optimize
it off.
I cannot explain this to me. When is the compiler optimizing away the
static member and not performing initialization?

Because it is allowed to optimize away entities that are not used
through out the program. Your trick to register is consider a side
effect and is therefore ignored.
Thanks a lot.

Markus


Regards,
Ben
 
M

Markus Henschel

What does the above (void)p do?

What I really wanted was to be able to execute code automatically at
program startup so it's sufficient to just write a class and it will
automatically insert a classinfo struct into a global list that can be
browsed at runtime. The struct would contain a factory function. So a
class instance can be created without knowing the class.

By deriving from a class template a static member of type struct
classinfo is added to the class. In the constructor of classinfo I
added a pointer to it to a global list.

The problem I'm facing is that the static struct might not be
initialized and created if the class itself is not really used.
 
B

benben

Just take its address.
class CTest : public THelper<CTest>
{
public:
static void Register()
{
void* p = &THelper<CTest>::ms_Dummy; (void)p;
printf("Registering CTest\n");
}
};

This won't work. Guess what? THelper<CTest> IS instantiated. But hey
hello optimizer takes away its members after it is instantiated.

If your optimizer is powerful enough you can actually use
THelper<CTest>::GetDummy in main and still no function gets registered. :)

In my case I need to actually write into THelper<CTest>::ms_dDummy to
get what the OP desires to:

int main(const char * args[], int iNumArgs)
{
CTest t;
t.ms_bDummy = true; // bust the optimizer

printf("There are %d functions registered.\n",
CMgr::GetRegisterList().size());

return 0;
}

Regards,
Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top