I am trying to make something like a Java style Enum, which I'm calling a flag. The requirements are that each flag is static so flags are directly referencable, each flag storing the string of it's name and the whole set iterable and conducive to lookups.
I am using templating so that each set of flags is stored separately (thus saving me from having to explicitly place a set in each child class).
I am convinced this is an initiation problem because the success or failure of running the program depends on the filename of the object file which contains the 开发者_运维问答flag declarations (A.o segfaults but Z.o runs fine.)
The problem seems to be one of static initialization order, this code compiles perfectly fine but when it is run, gdb produces the following:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff751e0fa in std::_Rb_tree_decrement(std::_Rb_tree_node_base*) ()
from /usr/lib/gcc/x86_64-pc-linux-gnu/4.4.5/libstdc++.so.6
(gdb) bt
#0 0x00007ffff751e0fa in std::_Rb_tree_decrement(std::_Rb_tree_node_base*) ()
from /usr/lib/gcc/x86_64-pc-linux-gnu/4.4.5/libstdc++.so.6
#1 0x0000000000462669 in operator-- ()
at /usr/lib/gcc/x86_64-pc-linux-gnu/4.4.5/include/g++-v4/bits/stl_tree.h:199
#2 _M_insert_unique ()
at /usr/lib/gcc/x86_64-pc-linux-gnu/4.4.5/include/g++-v4/bits/stl_tree.h:1179
#3 insert () at /usr/lib/gcc/x86_64-pc-linux-gnu/4.4.5/include/g++-v4/bits/stl_set.h:411
#4 Flag () at include/../util/include/Flag.hpp:34
#5 ItemFlag () at include/Item.hpp:22
#6 __static_initialization_and_destruction_0 () at Item.cpp:15
#7 global constructors keyed to _ZN3code8ItemFlag5brickE() () at Item.cpp:86
#8 0x000000000046ac62 in ?? ()
#9 0x00007fffffffddc0 in ?? ()
#10 0x000000000046abb0 in ?? ()
#11 0x0000000000692c0a in ?? ()
#12 0x0000000000407693 in _init ()
#13 0x00007ffff7dded08 in ?? () from /usr/lib64/libboost_serialization-1_42.so.1.42.0
#14 0x000000000046abe7 in __libc_csu_init ()
#15 0x00007ffff6cd9b50 in __libc_start_main () from /lib64/libc.so.6
#16 0x0000000000408329 in _start ()
My code is as follows:
template <class FlagType> class Flag
{
public:
Flag(int ordinal, String name):
ordinal(ordinal),
name(name)
{
flagSet.insert(this);
}
inline bool operator==(const Flag<FlagType>& e) const
{
//edited due to comment
//if(this->ordinal == e.getOrdinal()) return true;
//else return false;
return (this->ordinal == e.getOrdinal());
}
inline bool operator!=(const Flag<FlagType>& e) const
{
return !(*this==e);
}
static const std::set<const Flag<FlagType>*>& flagValues()
{
return flagSet;
}
const String& toString() const
{
return name;
}
const size_t& getOrdinal() const
{
return ordinal;
}
static int size()
{
return flagSet.size();
}
static const Flag<FlagType>& valueOf(const String& string)
{
typename std::set<const Flag<FlagType>*>::const_iterator i;
for(i = flagSet.begin(); i != flagSet.end(); i++)
{
if((**i).toString().startsWith(string))
{
return **i;
}
}
throw NotAFlagException();
}
protected:
static std::set<const Flag<FlagType>*> flagSet;
size_t ordinal;
String name;
private:
//added in response to comment to prevent copy and assignment, not compile tested
Flag<FlagType>(const Flag<FlagType>&);
Flag<FlagType>& operator=(const Flag<FlagType>&);
};
template <class FlagType> std::set<const Flag<FlagType>*> Flag<FlagType>::flagSet; //template
Item.hpp
class ItemFlag: public Flag<ItemFlag>
{
public:
static const ItemFlag brick;
private:
ItemFlag(int ordinal, String name):
Flag<ItemFlag>(ordinal, name){}
};
Item.cpp
const ItemFlag ItemFlag::brick(1, "brick");
My first post, so please let me know if I've got the formatting wrong or have been unspecific. PS. curiously, replacing set with vector results in a working program, as if the set is having trouble with inserting the pointers specifically. To test this I replaced the set with a set of int and tried to insert 0 on class initialization, this also resulted in the same error.
It could very easily be an order of initialization issue. You basically need to use some sort of lazy initialization for the set, e.g.
static std::set<Flag<FlagType> const*>& flagSet()
{
static std::set<Flag<FlagType> const*> theOneAndOnly;
return theOneAndOnly;
}
instead of your static variable.
And while I'm at it: this is probably not a good use of templates. A much better solution would be to generate the code from a file with a much simpler format, something along the lines of:
[EnumName]
constant_name_1
constant_name_2
etc. It would probably take no more than about 10 lines of AWK, Perl or Python (according to your tastes) to parse this and output both a C++ header and a C++ source file for it. You then only have to maintain the simple format.
If your class's constructor inserts items into the static set then its destructor should remove them. You also probably need a copy constructor and assignment operator. aso, on a point of style, protected data is normally considered A Bad Thing.
The order of static initialization between different translation units are not guaranteed. From the dump it seems like the ItemFlag
s are created before the set is constructed. That makes the insert fail.
Changing the name of the file just might affect the order of the file in the linking process. That would explain why a file starting with an A is linked in early.
The only way to make this work properly is to have the set and the ItemFlag
s defined in the same .cpp file. Then the order is always top-to-bottom. If they are in different files, the linker decides the order (mostly at random).
精彩评论