【问题标题】:std::set used as a static templated member variablestd::set 用作静态模板化成员变量
【发布时间】:2011-05-09 13:49:09
【问题描述】:

我正在尝试制作类似于 Java 风格的 Enum 的东西,我称之为标志。要求是每个标志都是静态的,因此标志可以直接引用,每个标志都存储它的名称字符串和整个集合可迭代并有利于查找。

我正在使用模板,以便单独存储每组标志(从而使我不必在每个子类中显式放置一组)。

我确信这是一个启动问题,因为运行程序的成功或失败取决于包含标志声明的目标文件的文件名(A.o 段错误,但 Z.o 运行良好。)

问题似乎是静态初始化顺序之一,这段代码编译得非常好,但是当它运行时,gdb 会产生以下结果:

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 ()

我的代码如下:

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

项目.hpp

    class ItemFlag: public Flag<ItemFlag>
{
public:

    static const ItemFlag brick;

private:

    ItemFlag(int ordinal, String name):
    Flag<ItemFlag>(ordinal, name){}
};

项目.cpp

const ItemFlag ItemFlag::brick(1, "brick");

我的第一篇文章,如果我的格式有误或不具体,请告诉我。 PS。奇怪的是,将 set 替换为 vector 会导致程序正常工作,就好像 set 在插入指针时遇到了麻烦。为了测试这一点,我用一组 int 替换了 set 并尝试在类初始化时插入 0,这也导致了同样的错误。

【问题讨论】:

  • if (…) return true; else return false; 是一种反模式。使用return …;!

标签: c++ templates enums static-initialization


【解决方案1】:

这很容易是初始化顺序问题。你基本上 需要对集合使用某种惰性初始化,例如

static std::set<Flag<FlagType> const*>& flagSet()
{
    static std::set<Flag<FlagType> const*> theOneAndOnly;
    return theOneAndOnly;
}

而不是你的静态变量。

虽然我正在这样做:这可能不是很好地使用模板。 一个更好的解决方案是从文件中生成代码 一种更简单的格式,类似于:

[EnumName]
constant_name_1
constant_name_2

等等。它可能需要不超过 10 行 AWK、Perl 或 Python(根据你的喜好)来解析这个并输出一个 C++ 头文件和一个 C++ 源文件。然后你只需要维护 简单的格式。

【讨论】:

  • 啊哈,懒惰的初始化正是我需要的,非常感谢。这种模板的使用确实看起来非常复杂,我猜这可能不是最好的主意。不过,我肯定会考虑学习 perl 并改用它。我会投票给你,但显然我没有这样做的声誉,所以我希望其他人都给你投票,直到我够了。
  • 我不相信这不是很好地使用模板。我可能对设计有一些详细的批评,但总的来说,我认为强类型枚举是 CRTP 的主要用例。
  • @user745247 如果您还不知道其中一种脚本语言,请不要学习 perl:-)。我使用 awk,但那是因为我早在 perl 或 Python 出现之前就学会了它;如果您是从头开始,我会推荐 Python(可能,基于我所听说的——我实际上并不知道它)。
  • @Konrad 我相信您可以使用模板来实现它,但为什么要这样做呢?在这种情况下,使用模板意味着您要为它们支付所有成本(可读性、编译时间等),并且不会真正获得任何好处:您不需要编译器内部的任何信息,所以没有当有非常简单的预编译器技术可用时,请指出使用过于复杂的编译时技术。
  • @James 好像预编译器也不会产生成本(维护、全面性、更复杂的构建系统、灵活性……)。模板非常优雅地解决了这个问题。同样,我将向您推荐 CRTP,其唯一目的是生成手头示例中所需的代码。编译时间在这里无关紧要:这可以被推到一个单独的编译单元(一次),所以它比任何你想命名的预编译器都便宜。最后,Java 通过 CRTP 实现其枚举。
【解决方案2】:

如果您的类的构造函数将项目插入静态集合,则其析构函数应该删除它们。您可能还需要一个复制构造函数和赋值运算符。同样,就风格而言,受保护的数据通常被认为是一件坏事。

【讨论】:

  • 我从未计划过要销毁静态标志,它们是类的静态 const 成员,因此从不调用析构函数。除非为了以后重新设计的完整性,我应该添加它。
  • @user 无论您是否计划将它们销毁,它们都会。如果您曾经将标志用作非引用函数参数或返回类型,它们也将被复制构造。
  • 我将添加一个私有复制构造函数和分配器,以防止生成更多实例,并始终通过引用引用标志,这应该确保唯一存在的标志是我静态分配的标志并且无处不在使用引用和指向它们的指针。
【解决方案3】:

不保证不同翻译单元之间的静态初始化顺序。从转储看来,ItemFlags 似乎是在构造集合之前创建的。这会导致插入失败。

更改文件名只是可能影响文件在链接过程中的顺序。这可以解释为什么以 A 开头的文件会在早期被链接。

使这项工作正常工作的唯一方法是将集合和ItemFlags 定义在同一个 .cpp 文件中。然后顺序总是从上到下。如果它们在不同的文件中,则链接器决定顺序(大部分是随机的)。

【讨论】:

  • 更改文件的名称,或使用 std::vector 作品,这让我觉得这是一个集合的特殊之处,但不管怎样,懒惰的初始化集合都有效。不幸的是,itemflags 在整个程序中被继承和使用,由于重新编译开销,将它们放在同一个文件中并不合适。
  • @user745247 - 全局变量的初始化分几个阶段执行,第一个阶段是将所有内存归零。如果这就是 std::vector 需要的所有默认构造函数,那可能会起作用。如果它碰巧做了一些需要动态初始化的事情,就像 std::set 似乎做的那样,你会再次遇到顺序问题。标准中没有任何保证,所以它是特定于实现的,也就是运气。
猜你喜欢
  • 1970-01-01
  • 2021-08-07
  • 1970-01-01
  • 2012-02-01
  • 2018-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多