【问题标题】:Why is there no "initialization" keyword in C++, as there is in Delphi?为什么在 C++ 中没有“初始化”关键字,就像在 Delphi 中一样?
【发布时间】:2010-12-09 20:53:47
【问题描述】:

在切换到 C++ 之前,我们发现 Delphi 中的 initialization 语言元素非常有用。它允许您在每个单元中都有代码,这些代码将在程序启动时被调用,因此您可以初始化该单元的各种元素。

在我们看来,这确实使事情变得更容易,并有助于保持代码整洁。

  • 那么为什么 C++ 中没有 initializationfinalization 呢?

  • 在 C++ 中替换此语言功能的选项有哪些?

【问题讨论】:

  • 在设计 C++ 时没有人考虑过它!我一直很喜欢 Java 静态代码块(它似乎与初始化具有相同的效果,尽管它们没有静态终结的等效项)。
  • 这绝不是 C++ 中最糟糕的问题。你真的应该切换回 Delphi。

标签: c++ delphi initialization


【解决方案1】:

等效的 C++ 特性是文件范围/全局对象的构造函数和析构函数。例如:

#include <iostream>
using std::cout;

struct X {
   X() { cout << "X::X()\n"; }
   ~X() { cout << "X::~X()\n"; }
};

static X x;

int main() { cout << "main()\n"; return 0; }

会输出

X::X()
main()
X::~X()

运行时。

通常认为使用此功能是不明智的,因为您无法控制这些构造函数和析构函数的执行顺序,这意味着事物可能会在它们的依赖项之前被初始化,从而产生难以调试的崩溃。

【讨论】:

  • 您可以完全控制订单(如果您将它们全部放在同一个翻译单元中)。
  • 您唯一无法控制的是翻译单元的处理顺序,但在翻译中,它们是按照声明的顺序处理的,IIRC。因此,只要您的静态不依赖于另一个翻译单元中的任何内容,就可以了。
  • 如果您可以将它们全部放在同一个翻译单元中,您也可以轻松地将它们全部放在main() 的顶部。
【解决方案2】:

在 C++ 中,构造函数/析构函数对通常用于这类事情。但是,在使用静态对象时要小心。如果你想这样做,你应该阅读两件事:

  1. What's the "static initialization order fiasco"?

  2. How do I prevent the "static initialization order fiasco"?

【讨论】:

  • 静态初始化命令惨败也困扰着Delphi
  • @David:你什么意思?静态初始化顺序惨败是由于 C++ 中没有明确定义的静态初始化顺序造成的。在 Delphi 中,它有明确的定义:单元按照它们编译的顺序进行初始化,由 uses 子句确定,并以相反的顺序完成。这就是为什么静态初始化顺序惨败影响Delphi。
  • @Mason 我想你可能一直过着隐蔽的生活!在实践中,试图从源代码中计算出初始化顺序需要了解编译器/链接器使用的未记录算法。更重要的是,对任何规模很大的项目进行计算都是不切实际的。实际上,单位使用条款的微小变化可能会导致初始化顺序的非常不可预测的变化。所以,我重申这是 Delpi 中的一个问题。
  • @David:请不要传播 FUD。我在工作中从事的项目接近 4 MLOC,总共有几千个单位,而且我在解决初始化顺序时从来没有遇到任何问题,但它已经成为一个问题。 (您不需要计算出 整个 顺序,只需确定单元 A 是在 B 之前还是之后初始化,这可以很容易地明确说明。)并且描述了编译器的“未记录的算法”在官方文档中:docwiki.embarcadero.com/RADStudio/en/…
  • @Mason 这里没有 FUD,而且我知道您是专家,所以您不需要通过折腾 MLOC 来证明这一点!考虑 4 个单元,A、B、C 和 D。C 使用 A,然后 B。D 使用 B,然后 A。A 在 B 之前初始化,反之亦然?
【解决方案3】:

问题一:为什么没有关键字?

除了 Stroustrup 或委员会成员之外,没有人能真正回答为什么 C++ 是这样的,但我们可以推测,可能它被认为对于关键字来说不够重要。 C++ 标准确实讨论了初始化的顺序,例如对象的顺序,但顺序没有严格定义并留给实现。这是一个引用(3​​.6.2/3):

是否由实现定义 或不动态初始化 (8.5, 9.4, 12.1, 12.6.1) 对象 命名空间范围在 main 的第一个语句。如果 初始化被推迟到一些 第一个之后的时间点 主要声明,它会发生 在第一次使用任何功能之前 或在同一个中定义的对象 翻译单元作为对象 初始化

问题2:如何实现Delphi中initializationfinalization关键字的等效?

有两种选择。第一个已被其他发帖者提到,我不想复制他们的答案:在某个范围内(translation unit 或命名空间)声明一个对象,它的构造函数和析构函数将被称为“有时”;在那里工作。

请注意,此顺序是实现定义的,因此您已经处于不确定的领域。

第二个选项也取决于编译器。您使用的是 Delphi,所以我认为您使用 C++ Builder 来编译您的 C++ 代码是否正确?如果是这样,C++ Builder 和其他一些编译器支持#pragma startup and #pragma exit pragmas。当您的程序启动或关闭时,这些 pragma 会在特定时间调用方法。

我个人认为这是一个更简洁的解决方案,原因有两个:

  • 它准确地指定某事何时发生 会发生,我可以看到它写 在代码中

  • 它允许您调用 函数,而不是使用 构造函数或析构函数。这是 美学上更干净,让你写,说, initialization()finalization() 方法 执行你的工作。 这可能会让您尽可能接近 Delphi 语法。

您可以使用这些 pragma 调用一个过程(不接受参数并返回 void),还可以选择使用 64 到 255 之间的数字指定它应该发生的时间。只有在初始化顺序或定稿很重要。首先调用较大的数字,并保留 0-63 的优先级。例如:

void initialization(void) { foo = 3; bar = 5; /* Do useful work here */ }
#pragma startup initialization 200

void finalization(void) { foo = 0; bar = 0; /* Do useful work here */ }
#pragma exit finalization 200

调用链由链接器管理,如果您使用更多特定于编译器的构造,例如weak packaging,您可能会遇到问题,但总的来说这是我推荐的技术。

【讨论】:

    【解决方案4】:

    类具有可用于初始化和清理的构造函数和析构函数。

    我认为在 C++ 中最接近“代码单元”的是类。

    【讨论】:

      【解决方案5】:

      使用构造函数(initialization 代码)和析构函数(finalization 代码)编写一个类。声明这个类的一个单例实例;构造函数在启动时被调用,析构函数在程序关闭前被调用。

      【讨论】:

      • 具有相同的效果,但有点hackey(如果你构建一个静态库,那么如果没有额外的布线,这可能无法工作)。
      【解决方案6】:

      通常,它被视为 C++ 中的代码异味,具有需要构造或销毁的全局状态,即使您确实有这个,您也只需在其构造函数中声明一个执行此操作的类并定义一个文件全局实例。

      【讨论】:

      • +1:全局状态不好。但它并不总是回合状态。也许是决定加载哪种语言 dll 的代码(这不是我的想法)。
      • @DeadMG 你说这被视为代码异味,但我不相信。比如说,一个 Windows 临界区呢?这通常是需要构建和销毁的全局状态。
      • 地球上的任何程序都必须有全局信息/数据/状态。问题是可执行代码太复杂,DLL/LIB中这类代码作者一定要小心
      • @David:真的吗?因为 WinAPI 线程函数给​​你一个 void*.用它。或者使用非极其原始的线程库,如 TBB 或 VS ConcRT。 @APZ28:那里没有分歧——除了几乎所有必要的全局程序状态都由编译器处理,比如堆和函数地址等等。程序员不应该需要为大量、绝大多数、绝大多数系统创建全局状态。
      • @DeadMG 我认为您永远无法使用关键部分。它们的全部意义在于它们是共享的。如果您不分享它们,那么它们将不起作用!
      【解决方案7】:

      在 C++ 中,您调用构造函数(相当于您的析构函数)与您的类同名,而析构函数与您的类同名,并以波浪号 (~) 为前缀:

      Class Point {
          public:
          Point() {  }
          ~Point() { }
      }
      

      【讨论】:

        【解决方案8】:

        C++ 最接近您所使用的特性是静态变量(特别是静态成员变量)。

        // A.h
        class A
        {
        public:
        
        private:
            static int someValue;
        };
        
        // A.cpp
        int A::someValue = 2;
        

        静态变量在程序启动时初始化。静态成员没有自动“终结”过程(您必须编写自己的清理函数并调用它)。

        【讨论】:

          猜你喜欢
          • 2011-11-01
          • 1970-01-01
          • 1970-01-01
          • 2018-10-28
          • 1970-01-01
          • 2020-04-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多