【问题标题】:error: anachronistic old-style base class initializer错误:不合时宜的旧式基类初始化程序
【发布时间】:2025-12-13 13:05:02
【问题描述】:

以下代码在我尝试过的所有 GCC 版本(在 C++98、C++11 和 C++14 模式下)都会产生后续编译错误:

struct T
{
    T(void* x) : (x) {}
};

// main.cpp: In constructor 'T::T(void*)':
// main.cpp:3:18: error: anachronistic old-style base class initializer [-fpermissive]
//      T(void* x) : (x) {}
//                   ^
// main.cpp:3:16: error: unnamed initializer for 'T', which has no base classes
//      T(void* x) : (x) {}

当然,这显然是错误的代码,因为我实际上并没有初始化任何东西。

但是为什么它是一个基类初始化器,为什么它是“不合时宜的”,而不是简单的错误?它曾经有效吗?什么时候?它是什么意思?


我在网上发现的only related references 有人在成员名称意外宏化时遇到错误,实际上导致与上面相同的代码:

#define bar
// ^ some library could have done this

struct T
{
    T(int x)
        : bar(x)   // effectively just `: (x)`
    {}

    int bar;       // will cause its own error
};

那些人从来没有发现错误的含义,尽管他们后来至少发现了他们的程序被破坏的原因。

【问题讨论】:

  • 我会猜测这个语法是 C 中带有类的基类初始化语法,就在最早的 [非标准] C++ 引入多重继承之前。
  • 这对我来说似乎也是最合理的假设。不幸的是,我无法通过通常的渠道找到“C++ 编程语言”的第一版或第二版。不过,我敢肯定 SO 上有人有。
  • 寻找“注释参考手册”(ARM)也许?
  • 把所有 C++ 标签放在一个问题上的好借口 :-)

标签: c++ c++11 gcc c++14 c++03


【解决方案1】:

在第一个 C++ 编译器 CFront 的 1984-5 版本的文档中找到:

构造函数可以这样写:

  vec.vec(int lb, int hb) : (hb-lb+1)
  {
      if (hb-lb<0) hb = lb;
      low = lb;
      high = hb; 
  }

构造:(hb-lb+1) 用于指定基类构造函数vector()所需的参数列表。

有道理,如果您考虑一下。大概添加了基类的显式命名以支持多重继承。 (当时不支持成员初始化列表——成员是无条件默认构造的——所以在多重继承之前,构造函数可以在那里初始化一件事。)

感谢 http://www.softwarepreservation.org/projects/c_plus_plus/ 归档文件。

...哇,我刚刚意识到“CFront”是文字游戏。

【讨论】:

  • 在我们之间这是一个完美的二分法。 :) “C++ 2.0”在 1989 年对其进行了更改,以支持多重继承。
【解决方案2】:

确实,这不是有效的标准 C++,因此我们必须查看该语言的历史编年史,以找出其失效的点。

在 1989 年,自 1985 年最初以 C++ 命名以来,在进一步定义“C++”时,Stroustrup 宣布基本初始化已从该语言的先前版本发生变化,以应对多重继承:[1]

[p191] C++ 编程语言 [Stroustrup 1986] 描述了在 1985 年 8 月定义和实现的 C++。本文描述了该语言从那时起的发展,并阐明了定义中的几点。需要强调的是,这些语言修改是扩展; C++ 已经并将继续是适合长期软件开发的稳定语言。 C++ 的主要新特性有:多重继承、类型安全的链接、更好地解析重载函数、递归定义赋值和初始化、更好的用户定义内存管理设施、抽象类、静态成员函数、const 成员函数、受保护的成员、运算符-&gt; 的重载以及指向成员的指针。 这些功能在 C++ 2.0 版本中提供。

[p214] 初始化基类和成员的语法已经 扩展以应对多重继承和初始化顺序 已经被更精确地定义了。 [..]

本文继续演示了我们目前熟悉的基类初始化语法,正如 Sneftel 已经指出的那样(省去了我翻阅任何更多旧文档的麻烦!),事实并非如此直到 1985 年,在最初的 C++ 实现中,它本身是从“C with Classes”演变而来的。因此,我们可以得出结论,C++ 2.0 在 1989 年引入了更熟悉的语法,而这个“不合时宜”的版本在此之前仍然有效。

当然,请注意,在问题的代码中,没有基础。因此,即使在 C++ 1.0 中,程序最终也不会成功编译。但是,我们发现了为什么会以这种方式解析语法。

值得注意的是,GCC 正在诊断晦涩的、被长期遗忘的语法,这种语法在近三十年的 C++ 版本中一直无效。


[1] “C++ 的演变:1985 年到 1989 年”,Bjarne Stroustrup,AT&T 贝尔实验室,1989 年; pdf

【讨论】:

    【解决方案3】:

    这在 ARM 的第 18.3.2 节中被明确描述为不合时宜。

    这些特性的原因通常是为旧版本的 C++ 或带有类的 C 提供连续性。所有的“时代错误”都有不受欢迎的特征。编译器不需要提供此类功能,但如果提供了,则有义务允许程序员停用它和/或警告使用它。

    【讨论】:

    • 你对第一个例子中equivalent to A(int x) : m(x) {}的说法有任何支持吗?
    • @Sjoerd 我认为没有。基类名称不是初始化语法的一部分的 C++ 版本不允许类具有不可默认构造的成员。