【问题标题】:What is the rationale for not having static constructor in C++?在 C++ 中没有静态构造函数的理由是什么?
【发布时间】:2011-07-15 04:08:24
【问题描述】:

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#)都有静态构造函数!

【问题讨论】:

  • 搜索“静态初始化订单惨败”。
  • @Nawaz:按什么顺序?这将如何与 C++ 之前的对象格式联系起来?
  • @Matti:你在谈论这个排序的东西,好像运行静态构造函数显然是不可能的。
  • @Matti:我想说的是在静态对象的构造函数之前(依次在main 之前执行),按照“实现定义”的顺序。我认为在 C++ 中使用静态构造函数没有什么不可能的,我认为他们只是忘记/不想给编译器增加额外的复杂性/假设 static 对象足以满足这些目的。
  • @Nawaz:谢谢。顺便说一句,static 对象初始化的“实现定义”顺序已经到位(参见第 3.6.2 节)。

标签: c++ constructor language-design language-features static-constructor


【解决方案1】:

静态意味着与对象无关的功能。由于只构造对象,因此静态构造函数有什么好处还不清楚。

您始终可以在静态范围内保存已在静态块中构造的对象,但您将使用的构造函数仍将被声明为非静态。没有规则表明您不能从静态范围调用非静态方法。

最后,C++/C 将程序的开始定义为在输入main 函数时。作为设置评估代码的“环境”的一部分,在进入 main 函数之前调用静态块。如果您的环境要求完全控制设置和拆卸,那么很容易争辩说它并不是真正的环境装置,而是程序的继承过程组件。我知道最后一点是一种代码哲学(并且它的基本原理可以有不同的解释),但是不应将关键代码放在“在”可执行文件将“完全控制”移交给编写的代码的正式开始之前由程序员编写。

【讨论】:

  • OP 指的是“静态初始化块”。例如,这是存在于 Java 中的东西。它允许您精确地执行 OP 想要的操作:初始化静态字段值。
  • 我说的是现在 C++ 中不存在的功能。但是您根据静态的当前含义来解释它不存在。所以你的解释似乎不合理。
  • @Nawaz,你说得对,但我扩展了我的陈述以包括 C / C++ 基本原理,说明为什么这样的项目仍然是一个坏主意。当然,它在某些情况下很有用,但它会在程序启动之前“推送”更多代码,从哲学的角度来看,这可能被视为无意义的解决方案,即使它有一些实际的好处。这是 C++ 的限制不符合您尝试设计的那些情况之一,但另一种方式可以很好地完成工作。
  • @Nawaz,考虑您的评论的含义。如果新功能不遵循 C++ 的当前定义,那很好,但话又说回来,新生成的语言也不能正确地称为 C++。我不认为他们可以在不保证静态环境中可用的构造函数的情况下合理地添加它。需要对目标文件加载方式的顺序进行一些改进。这样的排序意味着您可以强制将静态块的评估按特定顺序进行,这破坏了当今 C++ 设置其初始环境的大部分方式。
【解决方案2】:

这对 c++ 来说没有意义——类不是一流的对象(例如在 java 中)。

A (static|anything) 构造函数意味着某些东西是被构造的——而 c++ 类不是被构造的,它们只是被构造。

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

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

IMO 只是不需要另一种语法方式来做到这一点。

【讨论】:

  • +1:这与 Nawaz 提出的方法没有太大区别。不需要静态的“构造函数”。
  • @knivil:只是看起来有点杂乱无章。我的意思是,你在别处写了一个函数createStrings(),然后从这里调用它;让事情变得有点难以理解和管理!
  • @Nawaz:C++ 从来没有因为它漂亮且易于理解的语法而闻名;)
  • @Nawaz:我宁愿认为他们当时并不了解,或者无法找到使其向后兼容的方法。
  • @Matthieu:你的评论对我来说很有意义。我很欣赏你评论中的诚实。 :D
【解决方案3】:

使用静态初始化顺序问题作为不向语言引入此功能的借口一直是现状问题 - 没有引入它是因为它没有被引入,人们一直认为初始化顺序是一个不介绍它的理由,即使订单问题有一个简单且非常直接的解决方案。

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

//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;
  }
}

所以答案是,没有理由不存在,至少不是技术原因。

【讨论】:

  • 你如何解决链接到一些也有 static_main 的库的问题? static_main 调用的顺序是什么?
  • 就像 main;图书馆不应该有一个
  • +1 说这个 “使用静态初始化顺序问题作为不向语言引入此功能的借口一直是现状问题 - 它没有被引入因为它没有被引入,人们一直认为初始化顺序是不引入它的一个理由,即使顺序问题有一个简单而直接的问题”
  • @lurscher:但是你如何控制库中的静态初始化顺序呢?我同意静态初始化顺序是一个愚蠢的问题。
  • 大多数库都需要在main()中进行一些初始化代码才能使用;即使一个库被其他库使用,通常保证初始化只发生一次的唯一方法是将这种初始化委托给全局 main()。具有指定静态构造函数的语法并不能神奇地解决这个问题,而是使初始化要求明确——你忘了调用你的静态 ctor?嘿,编译器会给出一个理智的警告
【解决方案4】:

你可以把你的“静态”成员放在他们自己的类中,用他们自己的构造函数来执行它们的初始化:

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 之前,但不一定)

【讨论】:

    【解决方案5】:

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

    一旦您考虑到必须将静态变量放置在一个(并且只有一个)TU 中这一事实,那么接下来就不会“非常困难”了,并在函数中为它们分配值:

    // .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_init 的代码出现在类sample 的定义中,那么您甚至可以将其作为嵌套类放在那里。您只需在定义静态的相同位置定义它的实例(以及 它们已通过其默认构造函数初始化,否则您当然不能push_back 任何东西)。

    C# 是在 C++ 之后 15-20 年发明的,具有完全不同的构建模型。毫不奇怪,它提供了不同的功能,而且 C++ 中的某些事情不像 C# 中那么简单。

    C++0x 添加了一项功能,可以更轻松地使用一些数据初始化向量,称为“初始化列表”

    【讨论】:

      猜你喜欢
      • 2011-08-13
      • 2011-05-29
      • 2017-07-04
      • 2011-10-09
      • 2013-12-25
      • 2011-09-24
      • 2010-11-29
      • 2012-09-05
      • 1970-01-01
      相关资源
      最近更新 更多