C++中为什么没有静态构造函数?

31

为什么C++没有静态构造函数?

如果允许静态构造函数存在,我们会在其中以一种非常有组织的方式初始化所有静态成员,例如:

//illegal C++
class sample
{
public:

    static int some_integer;
    static std::vector<std::string> strings;

    //illegal constructor!
    static sample()
    {
       some_integer = 100;
       strings.push_back("stack");
       strings.push_back("overflow");
    }
};

如果没有静态构造函数,那么很难拥有静态向量,并像上面所示那样用值填充它。 静态构造函数优雅地解决了这个问题。 我们可以以非常有组织的方式初始化静态成员。

那么C++为什么没有静态构造函数呢? 毕竟,其他语言(例如C#)有静态构造函数!


5
搜索"static initialization order fiasco"。 - Hans Passant
1
@Nawaz:按什么顺序?这如何与来自C++之前的对象格式相关联? - Matti Virkkunen
1
@Matti:你说这个排序东西好像静态构造函数显然无法运行。 - Nawaz
1
@Matti:我认为静态对象的构造函数会在主函数之前执行,而它们的执行顺序是“实现定义”的。我认为在C++中加入静态构造函数并不困难,只是编译器可能觉得增加这种复杂性没有必要/认为使用“静态”对象已经足够了。 - Matteo Italia
1
@Nawaz:谢谢。顺便说一下,对于 static 对象初始化,“实现定义”的顺序已经存在于 §3.6.2 中。 - Matteo Italia
显示剩余6条评论
6个回答

23

把静态初始化顺序问题作为不引入此功能的借口,一直以来都是个墨守成规的问题 - 它之所以没有被引入是因为它没有被引入,人们一直认为初始化顺序是不引入它的理由,即使这个顺序问题有一个简单而直接的解决方案。

如果人们真的想解决初始化顺序问题,他们本可以有一个非常简单和直接的解决方案:

//called before main()

int static_main() {

ClassFoo();
ClassBar();

}

使用适当的声明:

class ClassFoo {
 static int y;
  ClassFoo() {
   y = 1;
  }
}

class ClassBar {
  static int x;
  ClassBar() {
   x = ClassFoo::y+1;
  }
}

因此,答案是,至少从技术上讲,并没有什么原因它不存在。


你如何解决链接到一些也有静态主函数的库?静态主函数调用的顺序是什么? - Erik
2
就像主函数一样,库文件也不应该只有一个。 - lurscher
2
将“使用静态初始化顺序问题作为不引入此功能到语言中的借口,一直以来都是现状的问题 - 它没有被引入是因为它没有被引入,人们仍然认为初始化顺序是不引入它的原因,即使顺序问题有一个简单而非常直接的解决方案”翻译成中文。 - Nawaz
这意味着所有全局变量/静态变量都必须在同一个编译单元中进行初始化?据我所知,只需在一个编译单元中对它们进行初始化是没有问题的。你不需要为此新建语法。 - UncleBens
1
@lurscher:但是在库中如何控制静态初始化顺序呢?虽然我同意静态初始化顺序是一个愚蠢的问题。 - Erik
5
大多数库需要在main()函数中进行一些初始化代码才能使用;即使一个库被其他库使用,通常保证只初始化一次的唯一方法是将这种初始化委托给全局main()函数。虽然有用于指定静态构造函数的语法,但它并不能神奇地解决这个问题,而是使初始化要求明确化——如果忘记调用静态构造函数?那么编译器会发出合理的警告。 - lurscher

16

这对于C++来说并不是很合理 - 类不是一等对象(像Java中的类一样)。

(静态的|任何)构造函数意味着有东西被构造 - 而C++类并没有被构造,它们只是存在。

但您可以轻松地实现相同的效果:

//.h
struct Foo {
  static std::vector<std::string> strings;
};
//.cpp
std::vector<std::string> Foo::strings(createStrings());

在我看来,没有必要再使用另一种语法方式来做这个。


+1:这与Nawaz提出的方法并没有太大的区别。不需要静态“构造函数”。 - knivil
@knivil:只是看起来有点不整洁。我的意思是,你在其他地方编写了一个名为createStrings()的函数,并从这里调用它;这使得事情变得有点难以理解和管理! - Nawaz
4
@Nawaz: C++从来不以其美观易懂的语法而闻名 ;) - Erik
@Erik::D.. 人们把事情搞得很复杂,然后自认为是极客 :D - Nawaz
5
@Nawaz:我更愿意认为当时他们不知道更好的方法,或者无法想出一种向后兼容的方式。 - Matthieu M.
1
@Matthieu:你的评论很有道理。我非常感谢你的诚实评论。:D - Nawaz

6

静态对象会被放置在哪个翻译单元中?

一旦您考虑到静态对象必须放置在一个(且仅一个)翻译单元中,那么在函数中为它们分配值就不是“非常困难”的事情了:

// .h
class sample
{
public:
    static int some_integer;
    static std::vector<std::string> strings;
};

//.cpp

// we'd need this anyway
int sample::some_integer;
std::vector<std::string> sample::strings;

// add this for complex setup
struct sample_init {
    sample_init() {
       sample::some_integer = 100;
       sample::strings.push_back("stack");
       sample::strings.push_back("overflow");
    }
} x;

如果你真的希望在类sample的定义中出现sample_init的代码,那么你甚至可以将它放在其中作为一个嵌套类。你只需要在定义静态变量的地方定义它的实例(并且要在它们通过默认构造函数进行初始化之后,否则当然就不能使用push_back了)。
C#是在C++之后15-20年发明的,并且有完全不同的构建模型。并不奇怪它提供了不同的特性,而且一些东西在C++中可能不像在C#中那么简单。
C++0x添加了一些功能来更容易地用一些数据初始化向量,称为“初始化列表”。

5
你可以将“静态”成员放在自己的类中,并编写一个构造函数来执行它们的初始化:
class StaticData
{
    int some_integer;
    std::vector<std::string> strings;

public:
    StaticData()
    {
       some_integer = 100;
       strings.push_back("stack");
       strings.push_back("overflow");
    }
}

class sample
{    
    static StaticData data;

public:
    sample()
    {

    }
};

您的静态data成员保证在您首次尝试访问它之前进行初始化。(可能在main函数之前,但不一定)


2

静态表示与对象无关的函数,因为只有对象被构造,所以并不明显为什么静态构造函数会有任何好处。

你可以始终在静态作用域中保存一个在静态块中构造的对象,但你将使用的构造函数仍将声明为非静态函数。没有规定表明您不能从静态作用域调用非静态方法。

最后,C++/C将程序的开始定义为进入main函数时。静态块在调用“环境”中的设置之一时在main函数进入之前被调用。如果您的环境要求完全控制设置和拆除,则很容易争辩说它实际上不是环境固件,而是程序的继承过程组件。我知道最后一点有点像代码哲学(它的理由可能会被解释得不同),但是程序员编写的代码应该不会将关键代码放在可执行文件“全权”移交之前。


1
OP所指的是“静态初始化块”。这是Java中存在的一种功能。它允许你做到OP想要的事情:初始化静态字段的值。 - Vojislav Stojkovic
我正在谈论C++现在没有的一个特性。但你解释它不存在是基于当前static的含义,所以你的解释似乎不太合理。 - Nawaz
@Nawaz,你是对的,但我扩展了我的陈述,包括了C/C++的理由,说明为什么这样的项仍然是个坏主意。当然,在某些情况下它很有用,但它将更多的代码“推到”程序开始之前,从哲学的角度来看可能被视为无意义的解决方案,即使它有一些实际的好处。这是其中一种情况,其中C ++的限制不符合您试图设计的内容,但是另一种方法可以很好地完成工作。 - Edwin Buck
@Nawaz,思考你评论的影响。如果新功能不遵循当前的C++定义,那么没关系,但是,新生成的语言也不能被称为C++。我认为他们不能在没有保证静态环境中存在构造函数的情况下合理地添加这个功能。需要改进以某种有序的方式加载对象文件的顺序。这样的排序意味着您可以强制对静态块进行某种顺序的评估,这破坏了C++设置其初始环境的大多数方法。 - Edwin Buck

1
除了其他答案,我认为这个功能是必需的。在使用静态成员或创建对象之前,必须调用静态构造函数,即在使用类之前。
我们正在编写一个用于初始化静态成员并调用该函数的静态函数。所以,我认为有静态构造函数会更好。 如何初始化类的静态成员?

问题是“为什么没有静态构造函数?”,而不是“为什么应该有?”。 - undefined
@BoP 我认为应该是这样的。 - undefined
StackOverflow的理念是,当你写答案时,你应该真正回答问题。这不是一个讨论论坛,而是一个问答网站。你也可以考虑到其他答案都是2011年的,所以你来得有点晚了。 - undefined

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