C++ コンパイラが仮想関数の呼び出しを非多態化(devirtualize)できるのは、主に以下の条件を満たす場合です。

*   **非多態性のクラス(Non-polymorphic class)**:コンパイラが、「このポインタで指し示されるオブジェクトは必ず A クラスそのものか、あるいはその派生クラスのいずれかである」と保証できるケースです。  
    具体的には、  
    - その関数が「純粋に仮想ではない(pure virtual な関数を呼ばない)」こと、  
    - また是该クラスから派生したすべてのサブクラスにおいて、虚関数テーブルへのポインタ(vptr)の解釈が一致していること  
    を意味します。

*   **完全な多態性が存在しない場合**:クラスの定義が完全に可視であり、かつそのクラス内で他の仮想関数が存在しないなどの状況で、コンパイラが静的に呼び出し元と呼び出し先を特定できる場合に該当します。

*   **`override` 指定子が付いていない場合、または派生クラスで再定義されていない場合**:静的なバインディング(static binding)が可能であれば、虚関数テーブルの参照を行わず、直近の実装を直接コールすることができます。

基本的に、コンパイラが「虚関数テーブルを介さずにコードジャンプ可能である」と判断した時点で devirtualize が発生します。

2026/05/17 17:58

C++ コンパイラが仮想関数の呼び出しを非多態化(devirtualize)できるのは、主に以下の条件を満たす場合です。 * **非多態性のクラス(Non-polymorphic class)**:コンパイラが、「このポインタで指し示されるオブジェクトは必ず A クラスそのものか、あるいはその派生クラスのいずれかである」と保証できるケースです。 具体的には、 - その関数が「純粋に仮想ではない(pure virtual な関数を呼ばない)」こと、 - また是该クラスから派生したすべてのサブクラスにおいて、虚関数テーブルへのポインタ(vptr)の解釈が一致していること を意味します。 * **完全な多態性が存在しない場合**:クラスの定義が完全に可視であり、かつそのクラス内で他の仮想関数が存在しないなどの状況で、コンパイラが静的に呼び出し元と呼び出し先を特定できる場合に該当します。 * **`override` 指定子が付いていない場合、または派生クラスで再定義されていない場合**:静的なバインディング(static binding)が可能であれば、虚関数テーブルの参照を行わず、直近の実装を直接コールすることができます。 基本的に、コンパイラが「虚関数テーブルを介さずにコードジャンプ可能である」と判断した時点で devirtualize が発生します。

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

現代のコンパイラは、オブジェクトの動的型が明示的に既知であるか、「葉(leaf)のような」構造として証明可能である場合(つまり、さらに継承が存在しないことを意味する)、仮想メソッド呼び出しを信頼性高く最適化するためにデバージリチュアライゼーションを実行できます。

final
とマークすることで継承を防止し、GCC、Clang、MSVC など主要なプラットフォームでこの最適化を一貫して確保する最も効果的な戦略です。高度なデータフロー解析は単一のコンパイルユニット内にある一部の複雑なシナリオに対処できますが、外部の翻訳ユニットには制限があり、全体プログラムのリンクタイム最適化(LTO)はこれらの知見に含まれていません。現在のコンパイラでは
final
キーワードを使用せずにプライベートなベースコンストラクタのみで葉性を証明することはできず、GCC の既存のバグが継承された final メソッドの最適化を妨げているため、明確な
final
宣言に従うことが最も安全な道です。一貫したパフォーマンス向上を求めている開発者は、普遍的に動作しないかもしれない奇妙なコンパイラ固有のトリックよりも、明確な
final
の使用を優先すべきです。

Text to translate:

Modern compilers can reliably optimize virtual method calls through devirtualization when an object's dynamic type is explicitly known or provably "leaf-like"—meaning no further inheritance exists. Marking classes or methods as

final
, which prevents inheritance, is the most effective strategy for ensuring this optimization across major platforms like GCC, Clang, and MSVC. Advanced dataflow analysis can handle some complex scenarios within a single compilation unit but faces limitations with external translation units; whole-program Link-Time Optimization (LTO) is excluded from these findings. Although current compilers cannot rely solely on private base constructors to prove leafness without using the
final
keyword—and some existing bugs in GCC still hinder optimization of inherited final methods—adhering to explicit
final
declarations remains the safest path. Developers seeking consistent performance gains should prioritize clear
final
usage over obscure, compiler-specific tricks that may not work universally.

本文

最近、有人向我请教关于去虚化(devirtualization)优化的问题:它们何时发生?我们能在哪些情况下依赖去虚化?不同的编译器在实现去虚化时是否存在差异?这些问题如同往常一样,又将我带入了一个充满实验的“兔子洞”。

目前的结论似乎是:现代编译器对于最终方法(final methods)的调用进行去虚化的能力相当可靠。然而,其中仍存在许多有趣的边缘情况——我敢肯定其中有些是我尚未想到过的——而且不同的编译器所覆盖的边缘情况子集也各不相同。

首先,我们需要观察到的是:通过整程序分析(LTO),或许可以更有效地执行去虚化。不过,我对链接时去虚化领域的最新进展并不了解,且在 Compiler Explorer 上进行相关实验颇具困难,因此本文将完全不讨论 LTO。我们仅关注编译器本身所能实现的能力。

本质上,只有在以下两种情形中,编译器才拥有足够的信息来进行去虚化,且这两种情形并无太多共通之处:

  1. 当我们知道对象的动态类型时
    此类情况最典型的例子如下:

    void test() {
        Apple o;
        o.f();
    }
    

    在此情境下,《Apple::f》是否为虚函数并无影响;因为无论何种方式,所有涉及虚调用的操作都只会将方法实际调度到对象的真实动态类型上,而此处我们确切知道其动态类型就是

    Apple
    。因此,静态调用与动态调用的结果应完全相同。

    智能程度足够的编译器还会利用数据流分析来优化更具挑战性的情况,例如:

    Derived d;
    Base *p = &d;
    p->f();
    

    出人意料的是,就连这种简单的小技巧也足以欺骗 MSVC 和 ICC。

    再下一个测试用例如下:

    Derived da, db;
    Base *p = cond ? &da : &db;
    p->f();
    

    这种情况对 Clang 来说过于复杂而无法处理,但 GCC 竟然能应对……除非你将转换到

    Base*
    的操作移入条件表达式内部!此时即便是 GCC 的分析也会失效(参见 Godbolt):

    Derived da, db;
    Base *p = cond ? (Base*)&da : (Base*)&db;
    p->f();
    
  2. 当我们可以为其实例类型提供“无继承证明”时
    假设我们接收一个来自系统其他部分的指针。我们知道其静态类型(例如

    Derived*
    ),但并不知道它所指向对象的实际动态类型。尽管如此,如果编译器能够以某种方式证明在整程序中没有任何类型会覆盖
    Derived::f
    ,那么它仍然可以对
    Derived::f
    的调用进行去虚化。

    通过“最终性”证明(Proof-by-final)

    最简单的“无继承证明”方式是将某个类标记为

    final

    struct Base {
        virtual int f();
    };
    struct Derived final : public Base {
        int f() override { return 2; }
    };
    int test(Derived *p) {
        return p->f();
    }
    

    类型为

    Derived*
    的指针必然指向“至少是
    Derived
    "的对象实例——也就是说,该对象的动态类型要么是
    Derived
    ,要么是它的某个子类。但由于
    Derived
    被声明为
    final
    ,它不可能拥有任何子类;因此,该实例的动态类型必须严格等于
    Derived
    ,编译器据此即可对该调用执行去虚化。

    或者,你也可以将特定方法(如

    Derived::f
    )直接标记为
    final

    同样的分析逻辑无论该方法是在

    Derived
    中自行声明,还是从
    Base
    继承而来,都应适用。因此,编译器应同样能够处理如下情况:

    struct Base {
        virtual int f() { return 1; }
    };
    struct Derived final : public Base {};
    int test(Derived *p) {
        return p->f();
    }
    

    GCC、Clang 和 MSVC 均通过了此测试(参见 Godbolt case one);而 ICC 21.1.9 则被迷惑了。

    另一种极为奇特的“无继承证明”方式是:观察到当类 C 的析构函数被声明为

    final
    时,C 必须没有子类——因为如果 C 有子类,则该子类也必须具备析构函数(毕竟任何类都不可避免地拥有析构函数),而这会尝试覆盖 C 的析构函数,这是不允许的。Clang 实际上不仅会对“最终析构函数”发出警告,还会针对它们进行优化。据我所知,其他所有供应商都认为这种情况非常荒谬,并未为此提供专门的代码路径。

    通过“内部链接”证明(Proof-by-internal-linkage)

    如果一个类的名称具有内部链接(internal linkage),则该名称无法在当前翻译单元(translation unit)之外被引用。因此,它也不可能从当前翻译单元之外的地方被派生!只要在当前翻译单元内没有子类(至少是没有覆盖其方法的子类),对其虚函数的调用就可以进行去虚化:

    namespace {
        class BaseImpl : public Base {};
    }
    int test(Base *p) {
        return static_cast<BaseImpl*>(p)->f();
    }
    

    如果

    p
    确实指向“至少是
    BaseImpl
    "的对象实例,那么编译器可以证明该实例必须严格等于
    BaseImpl
    。(而倘若
    p
    并非指向一个“至少是
    BaseImpl
    ”的实例,那么程序本身就已处于未定义行为状态。)

    在我看来,这种情形在真实代码库中其实相当常见。通常做法是在头文件中公开暴露一个基类,而在单个

    .cpp
    文件中限定作用域地定义一或多个派生实现。如果你更进一步,将这些派生实现放入匿名命名空间(anonymous namespace),或许就能帮助编译器的去虚化逻辑。当然,按照定义,此类优化所带来的好处仅限于该单一
    .cpp
    文件之内!

    另一种使类型名称获得内部链接的方式是:当该类是一个模板实例化,且其中一个模板参数涉及具有内部链接的名称时。例如,若名称

    T
    具有内部链接,则即使
    E
    本身具有外部链接,
    E<T>
    也会自动具有内部链接——因为要使用
    E<T>
    ,就必须同时使用
    T
    。(请注意,此处要求
    T
    是一个“真名”,我们讨论的不是类型别名。)

    此外,还有一种可能性:构造一个其名称具有外部链接的类型,但编译器却能证明在其他所有翻译单元中该类型必然是不完整的。例如:

    namespace {
        class Internal {};
    }
    class External { Internal m; };
    

    其他任意翻译单元都可以对

    class External
    进行前向声明(作为不完整类型),但它们永远无法完成该类型的定义,因为它们无法为其数据成员指定具体类型。由于不能从不完整类型派生,因此所有从
    External
    派生的类型(如果存在的话)都必须出现在当前翻译单元中;若此处并无此类派生类型,那就构成了“无继承证明”!只有 GCC 能够识别这种情况。

    测试结果汇总表

    • Godbolt 提供了已知动态类型情形下的测试示例;
    • Godbolt 还提供了“无继承证明”情形的测试示例。在后者中,我分别为“直接在
      Derived
      中定义的
      Derived::f
      "和“从
      Base
      继承的
      Derived::g
      "设置了独立的测试。GCC 经常能正确判断
      f
      ,却未能对
      g
      执行去虚化。针对此问题,我已向 GCC 提交了 bug #99093。
    测试用例编号情况描述GCCClangMSVCICC
    onetrivial(平凡情况)
    twocast to Base*(强制转换为 Base*)
    threeconditional, then cast(条件判断后转换)
    fourcast, then conditional(先转换,再条件判断)
    one (final class)final class(最终类)f
    two (final method)final method(最终方法)
    three (silly final destructor)silly final destructor(荒谬的最终析构函数)
    foursilly old-school trick(愚蠢的老派技巧)
    fiveI.L. class(内部链接类)f
    sixI.L. template parameter(内部链接的模板参数)f
    sevenI.L. base(内部链接基类)f
    eightI.L. member(内部链接成员)f
    nineI.L. with child(带子类的内部链接类型)f
    tenlocal class(局部类)f

    Steve Dewhurst 教会了我“four”中的“愚蠢老派技巧”:虚拟基类总是在最派生类的上下文中构造。因此,若类 C 拥有一个虚拟基类,且该虚拟基类的所有构造函数均为私有的,则没有任何 C 的子类能够自行构造自己;换言之,C 的任何子类都无法存在。(当然,为了使 C 自身可被构造,该虚拟基类必须将 C 列为其友元。)我认为这一技巧是绝对可靠的,因此可以构成对 C 的“无继承证明”;然而显然,没有任何编译器愿意追溯这纷繁复杂的逻辑链条,即便它确实牢不可破。

    能否想到其他我尚未提及的、能够构造出“无继承证明”的方法?欢迎与我分享!

同じ日のほかのニュース

一覧に戻る →

2026/05/19 10:30

LLM による「過去六ヶ月の要約」――わずか五分で読み解く

## Japanese Translation: PyCon US 2026 における「2025 年 11 月の転換点」に関する振り返りは、AI ランドスケープが標準ハードウェア(例:Mac Mini)上で動作する実践的なローカルツールおよび個人用 AI アシスタントへと劇的に移行していることを示しました。2025 年後期から 2026 年初頭にかけての期間は、「最良」という称号を巡る過激な競争によって特徴づけられていました。11 月単独でわずか 2 ヶ月の間にトッププロバイダーにおけるリーダーシップは 5 回交代し、Claude Sonnet 4.5、GPT-5.1、Gemini 3、Codex Max などを経由した後、最終的に Claude Opus 4.5 に落ち着きました。この時代は、コーディングエージェントにおいて OpenAI や Anthropic の検証可能な報酬に基づく強化学習(Reinforcement Learning from Verifiable Rewards)への取り組みにより、単発的な動作から信頼できる日常利用ツールへと移行したことで推進されました。 顕著な技術的進展としては、Google が実用的なオープンウェイトモデルとして Gemma 4 シリーズをリリースしたことや、中国の研究所が GLM-5.1(1.5TB モデル)を公開したことが挙げられます。これらのモデルは、動物がエスクーターに乗っている様子やバージニア北部のカナザが自転車に乗っているような不可能なタスクのアニメーション生成といった驚くべき能力を発揮しました。特定のコミュニティプロジェクトは「Warelay」として始まりましたが、「OpenClaw」という名称を最終的に採用し、人気のあるローカル「個人用 AI アシスタント」の代名詞となりました。2026 年 2 月には新規モデルに対する需要が高まりシリコンバレーで Mac Mini が品切れになった一方で、一部のプロジェクトはセキュリティ懸念とパフォーマンスの問題のため廃止されました。全体のテーマは、自律的にホストされる知能のブームであり、ラップトップ搭載のモデルが業界リーダーと比較して期待を大きく上回る性能を発揮し始めた点にあります。

2026/05/17 1:49

Android スマートフォンを业余無線局トランシーバーに変えてください。

## Japanese Translation: kv4p HT は、Android スマートフォンとの統合を目的として設計されたオープンソースの VHF/UHF アマチュア無線トランスceiver です。専用バッテリーや外部充電器の必要性を排除するため、スマートフォンから電力を供給します。カスタム PCB(v2.0e)、SA818-V/U または DRA818V/U モジュール、SMA メスアンテナなどの部品の使用により構成され、SMS 風のメッセージングおよび位置情報ビーコン機能(APRS)を含む高度なデータ通信タスクをサポートします。法的に運用するためには、少なくとも技師級のアマチュア無線免許証を保有している必要があります。製品には保証がありません。フルデザインと GPL3 ライセンスの ESP32 ファームウェアは GitHub 上に公開されており、3D プリンター用ファイルも用意されているため、DIY による組み立てが可能で、モジュール/PCB のハンダ付け、接着ゲルパッドによる装着、3D プリント製ケースの取り付けを伴います。新規ユーザーは、事前に組み立て済みのベンダーキットを購入しない場合は、ソフトウェアを手動でフラッシュする必要があります。本システムは、2017 年以降の Android 8 以上のデバイスとのみ互換性があります。リアルタイムクローズドキャプション、PTT 用のハプティックフィードバック、アニメーション制御など、アクセシビリティ機能により、多様なユーザーにとって使いやすさが向上しています。

2026/05/19 13:24

コーデックス・マキシング(Codex-maxxing)

## Japanese Translation: この文は、短命なチャットセッションから、複雑な知識労働に適した耐久性のある長時間稼働型コーディングエージェントへの転換を描いています。これらのエージェントは、「compaction(コンパクト化)」という機能を用いて古いのメッセージを剪定し、コスト超過やコンテキスト制限を防ぎつつ不可欠な履歴を保持しながら、数ヶ月間自動化された動作を持続させます。ユーザーは「Chief of Staff」のようなメガスレッドをピン留めして好みを蓄積し、Command-1 から Command-9 などのショートカットを通じてワークストリームを舵取りできますが、オフキャッシュのスレッドは新規の短寿命スレッドよりも高いコストを支払う可能性があります。エージェントは Codex および Wispr Flow を通じて音声入力を受付けることができ、システム全体での口述が可能になり、タイピング単独よりも豊富なコンテキストを実現します。ユーザーはツール呼び出し後(steering)に新たな方向性を注入し、ステップが完了するのを待たずにエージェントを誘導できます。共有メモリシステムはチャット外に Obsidian クォート内にアーティファクト(AGENTS.md を含む)を保存しており、これらを検証、編集、差分表示することを可能にします;GitHub でリポジトリとしてホストされる場合、クラウドツールを通じてメモリの更新内容を確認でき、審査されていない対話の「vibes(雰囲気)」が蓄積するのを防ぎます。$slack、$gmail、$calendar、$browser、@chrome、および@computer といったコネクタは、ローカルのウェブ表面、認証済みのブラウザ状態、Twitter やデスクトップアプリなどの GUI アプリケーションなど、追加機能を提供します。Hatch Pet などのインストール可能なスキルは再利用可能なワークフローをパッケージ化し、エージェントが再教育なしでタスクを繰り返せるようにします。リモートコントロール機能により、ユーザーは作業マシン上で長時間稼働するタスクを開始し、モバイルデバイスからステップを承認することで進捗を持続させつつ管理できます。スレッドローカルハートビートは、Slack/Gmail を 30 分ごとに、フィードバックを 15 分ごとにといった再帰的なチェックをスケジュールし、ユーザーの常在なしでループを稼働させます。ゴールはエージェントに明確な仕上げラインと成功基準を与え、例えば元の単一テストを全てパスしながら Python Rich を Rust に移行する場合などに適用されます。サイドパネルはアーティファクト(Markdown、PDF、スプレッドシート)を検証し、ウェブ表面(index.html、Storybook、Slidev)を操作し、ループを壊さずに変更を確認する作業領域として機能します。

C++ コンパイラが仮想関数の呼び出しを非多態化(devirtualize)できるのは、主に以下の条件を満たす場合です。 * **非多態性のクラス(Non-polymorphic class)**:コンパイラが、「このポインタで指し示されるオブジェクトは必ず A クラスそのものか、あるいはその派生クラスのいずれかである」と保証できるケースです。 具体的には、 - その関数が「純粋に仮想ではない(pure virtual な関数を呼ばない)」こと、 - また是该クラスから派生したすべてのサブクラスにおいて、虚関数テーブルへのポインタ(vptr)の解釈が一致していること を意味します。 * **完全な多態性が存在しない場合**:クラスの定義が完全に可視であり、かつそのクラス内で他の仮想関数が存在しないなどの状況で、コンパイラが静的に呼び出し元と呼び出し先を特定できる場合に該当します。 * **`override` 指定子が付いていない場合、または派生クラスで再定義されていない場合**:静的なバインディング(static binding)が可能であれば、虚関数テーブルの参照を行わず、直近の実装を直接コールすることができます。 基本的に、コンパイラが「虚関数テーブルを介さずにコードジャンプ可能である」と判断した時点で devirtualize が発生します。 | そっか~ニュース