关于C++:静态变量的地址


Address of a static variable

我正在尝试进行一个简单的类到唯一ID的转换。我正在考虑添加静态方法:

1
2
3
4
5
6
class A {
  static int const *GetId() {
    static int const id;
    return &id;
  }
};

然后,通过唯一的int const *来标识每个类。这能保证工作吗?返回的指针是否真的是唯一的?有没有更好更简单的解决方案?

我还考虑过指向std::type_info的指针:

1
2
3
4
5
class A {
  static std::type_info const *GetId() {
    return &typeid(A);
  }
};

这样好些了吗?

编辑:

我不需要使用ID进行序列化。我只想标识一个小的基类集,我希望某个类的所有子类都具有相同的ID。


是的,这会奏效的。加载模块时,每个static本地将被赋予不同的内存位置,并将一直保持到卸载模块为止。记住,static局部变量存储在编译期间分发的静态存储中,它们一直保持到模块卸载,因此它们将具有不同的内存位置。


静态变量的地址保证在所有翻译单元中都是唯一和相同的。

这不是一个好主意,因为它要求您向要标识的每个类添加代码。

指向类型信息对象的指针不保证是唯一的,但类型信息对象本身保证在给定类中比较为相等,在不同类中比较为不相等。这意味着您可以使用带有类型信息指针的小型包装器对象,并将比较委托给类型信息对象。C++ 11在标准库中有这样一个包装器,如果你不能访问它,Andrei Alexandrescu的"现代C++设计"中有一个,因此也可能在洛基库中,可能有一个在Boost中,在我的WordPress博客上有一个。

但是,如果要将ID用于序列化,则需要跨生成有效的ID。在这种情况下,您需要字符串或UUID。我会和UUID一起去。

要将类与UUID关联,通常可以使用类型特征类。或者,如果你只做Windows编程,那么你可以使用VisualC++的语言扩展。我认为,但我不能百分之百地肯定这些语言扩展也是由g++实现的(在Windows中)。

干杯!


正如我注意到的,至少MSVC 2008或2010优化了静态变量,因此下面的GetId函数返回相同的地址,即使是对于不同的类。

1
2
3
4
static int const *GetId() {
    static const int i = 0;
    return &i;
}

因此,不能使用未初始化常量静态变量的地址进行标识。最简单的解决方法是只删除const

1
2
3
4
static int *GetId() {
    static int i;
    return &i;
}

生成ID的另一个解决方案是使用全局函数作为计数器:

1
2
3
4
int Counter() {
    static int i = 0;
    return i++;
}

然后在要标识的类中定义以下方法:

1
2
3
4
static int GetId() {
    static const int i = Counter();
    return i;
}

由于要定义的方法总是相同的,因此可以将其放入基类:

1
2
3
4
5
6
7
template<typename Derived>
struct Identified {
    static int GetId() {
        static const int i = Counter();
        return i;
    }
};

然后使用一个奇怪的循环模式:

1
2
3
class A: public Identified<A> {
    // ...
};


静态int的地址保证每个地址都是唯一的。函数(对同一函数的每次调用都相同)。像这样的,它可以在代码的一次执行中作为一个ID很好地工作。地址可以从一次运行更改为下一次运行,并且will经常从一个编译更改为下一个编译(如果已更改代码中的任何内容),因此对于外部ID来说,它不是一个好的解决方案。(您没有说ID在单个执行之外是否必须有效或者没有。

typeid结果的地址不能保证是每次调用函数时都是相同的(尽管它可能是这样)。但是,您可以使用它来初始化指针:

1
2
3
4
5
static std::type_info const& GetId()
{
    static std::type_info const* id = &typeid(A);
    return id;
}

与使用int*相比,这具有提供额外的信息(例如用于调试)。与int*一样,标识符可以是不同于一次运行到下一次运行;A::GetId()->name()将指向相同的'\0'终止字符串(尽管地址可能是不同)提供了使用相同的编译器进行编译。(就我而言)可以说,标准不能保证这一点,但在实践中,我认为你很安全。)不过,更改编译器,所有赌注都取消了。

我过去使用的解决方案是:

1
2
3
4
static char const* GetId()
{
    return"A";  //  Or whatever the name of the class is.
}

这在一个代码的执行,以及可以用作外部标识符,在所有编译器中都有保证。我们将其作为宏实现,宏定义了静态函数和返回它的虚拟函数,例如:

1
2
3
#define DECLARE_IDENTIFIER(name)                                    \
    static char const* classId() { return STRINGIZE(name); }        \
    virtual char const* id() { return classId(); }

这将导致非常快(但有限)的RTTI,并支持外部用于序列化和持久性的标识符。


显然,指向不同变量的指针必须具有不同的值。如果您选择派生a的子类,就要当心了。您需要决定ID的策略是什么。如果您什么都不做,那么子类将具有相同的ID。


int*方法将是唯一的,因为必须为每个静态变量分配一个不同的静态内存单元,我想理解它要比类型_info思想简单。


一般来说,你真的很想避免像这样的黑客行为。如果我真的必须这样做,我会考虑使用一些UUID系统(为此有一个库在Boost中,但我不太熟悉),或者使用一些单例来维护这些对象的列表,以满足您的任何需要。


静态变量在堆和堆栈内存之前初始化,所以它是唯一的。

虽然古怪。


因为必须将此方法添加到所有需要uid的类中,所以也可以这样做。

1
2
3
4
unsigned int getUID()
{
    return 12;
}

这样做的好处是,如果您将它用于RTTI以打开类型,编译器将能够使用跳转表,因为跳转表将非常稀疏,因此使用两个指针是不可能的。

(次要)缺点是您需要跟踪哪些标识符已被采用。

第一个方法的一个主要缺点是,不能使用相同的数字来标识对象,因为虚getuid()方法无法获取另一个函数作用域中变量的地址。