如何强制初始化静态成员?

26

考虑下面的示例代码:

template<class D>
char register_(){
    return D::get_dummy(); // static function
}

template<class D>
struct Foo{
    static char const dummy;
};

template<class D>
char const Foo<D>::dummy = register_<D>();

struct Bar
    : Foo<Bar>
{
    static char const get_dummy() { return 42; }
};

(也可在Ideone上查看。)

我本以为只要有一个具体实例化的Foo,比如Bardummy就会被初始化。但是这个问题(以及结尾处的标准引用)解释得很清楚,为什么这不会发生。

[...] 特别是,静态数据成员的初始化(及其相关副作用)只有在静态数据成员被使用需要存在静态数据成员的定义的方式时才会发生。

是否有任何方法可以强制dummy被初始化(有效地调用register_),而不需要任何BarFoo的实例(没有实例,所以没有构造函数),并且Foo的用户不需要以某种方式明确声明该成员?对于无需派生类进行任何操作的方法来说,额外的赞赏加饼干。


编辑找到了一种方法,对派生类的影响最小:

struct Bar
    : Foo<Bar>
{   //                              vvvvvvvvvvvv
    static char const get_dummy() { (void)dummy; return 42; }
};

虽然,我仍希望派生类不必这样做。 :|


1
@Bo:当然可以,但我想把这个用法从派生类/外部世界中隐藏起来,而是更愿意将其某种方式融入到 Foo 本身中。 :/ - Xeo
1
@Serge:为什么不行呢?get_dummy中的“dummy”是未初始化的,但这并不重要。 :) 我实际上也不使用它。 - Xeo
1
@Xeo: "get_dummy 中的 dummy 是未初始化的,但这并不重要。 :) 我实际上也不使用它。" 是的。但是为什么它应该强制 Foo<Bar> ::dummy 被初始化?而且,什么可以防止编译器优化语句 (void)dummy;(没有效果)呢? - Serge Dundich
1
@Serge:它强制dummy被初始化,因为它被引用了。关于优化,我需要检查一下。 - Xeo
1
@Xeo:“它强制使虚拟变量初始化,因为它被使用了。” 实际上不是这样的。只有在您在某个地方调用get_dummy()时才会使用它。静态成员和全局变量在main的第一条语句之前或在与该静态成员或全局变量的模板Foo特化实例化相同的翻译单元中定义的任何对象或函数的第一次使用之前进行初始化。因此,如果您不使用从实例化模板Foo的同一翻译单元中的任何对象或函数,则完全可以不初始化Foo<Bar> :: dummy。 - Serge Dundich
显示剩余8条评论
6个回答

12

请考虑:

template<typename T, T> struct value { };

template<typename T>
struct HasStatics {
  static int a; // we force this to be initialized
  typedef value<int&, a> value_user;
};

template<typename T>
int HasStatics<T>::a = /* whatever side-effect you want */ 0;

不引入任何成员也是可能的:

template<typename T, T> struct var { enum { value }; };
typedef char user;

template<typename T>
struct HasStatics {
  static int a; // we force this to be initialized
  static int b; // and this

  // hope you like the syntax!
  user :var<int&, a>::value,
       :var<int&, b>::value;
};

template<typename T>
int HasStatics<T>::a = /* whatever side-effect you want */ 0;

template<typename T>
int HasStatics<T>::b = /* whatever side-effect you want */ 0;

第一个似乎在GCC上运行良好(http://ideone.com/4t1mi),但在MSVC10上不行,没有初始化发生。 :/ 第二个在GCC和MSVC10上都不起作用。 :( 编辑:哦等等,启用C++0x后,它在GCC上运行良好(http://ideone.com/s85LF)。在MSVC上仍然没有运气... - Xeo
@Xeo,在我的gcc4.6上没有使用c++0x也可以工作。除了clang之外,我没有其他编译器可供尝试。在clang上,第二个也不起作用。PR正在路上。 - Johannes Schaub - litb
5
天啊!“user :var<int&, a>::value”是什么意思? - pure cuteness
4
这是一个未命名的位域,实际上相当于 char : 0; - Xeo

5
我们可以使用一个简单的技巧,基于必须使用类实例化的声明:
template<…>
struct Auto {
  static Foo foo;
  static_assert(&foo);
};
template<…> Foo Auto::foo=…;

请注意,一些编译器会警告与null的比较; 如果需要可以通过&foo==&foo(bool)&foo((void)&foo,true)避免这种情况。请注意,GCC 9.0-9.2 不将此视为odr-use

到目前为止,这是最优雅的选择,谢谢。我有点担心称它为“优雅”,但嘿,这是C++ :) - Mikhail
谢谢,但在Visual C++ 2019上无法工作。听起来像是Visual C++的错误。 - cppBeginner

0

我脑海中浮现出类似的东西:

// in some c++ file (to make i with internal linkage)
static int i = init_dummy(Foo<int>::dummy);

其中 init_dummy 的定义如下:

int init_dummy(...)
{
  return 1;
}

由于可变参数,您可以在那里放置更多的初始化,例如:

static int i = init_dummy(Foo<int>::dummy, Foo<double>::dummy, Foo<whatever>::dummy);

0

我最近遇到了这个问题。官方解决方案是显式模板实例化,可以在这里找到详细说明。

对于上述情况,语句应该如下所示:

template class Foo::Bar;

-1

你是如何检查Bar设置的值的。 我修改了你的代码,并在bar中添加了另一个函数:

....
static char const get_dummy(int){return Foo<Bar>::dummy;}
....

它正好给了我期望的结果。也许我没有理解正确,你想要实现什么?

静态成员在对象之间共享,因此它们的作用域必须在访问时解决。这就是为什么我们使用::来明确告诉编译器这是我们要访问的类的成员。


-2
有没有办法强制初始化虚拟对象(dummy)(有效地调用register_),而不需要任何Bar或Foo的实例(没有实例,因此没有构造函数技巧)?
这样做不就足够了吗?
std::cout << Foo<int>::dummy;

我希望有其他方法,而不是让用户明确地声明成员。 :| - Xeo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接