You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编译器为何允许用using指令为不完整类型的模板创建别名?

关于Attorney-Client惯用法中不完整类型使用的C++标准合规性问题

这段代码简化自Attorney-Client idiom(律师-客户惯用法),用于限制仅特定类可调用目标类的成员函数。以下是完整代码实现:

文件header_A.h

// forward declaration(前向声明)
struct B;

template <typename T>
class Tag {
    friend T;
    // 仅T类型可构造tag
    Tag() = default;
};

struct A {
    int val = 0;
    using tag = Tag<B>; // B是不完整类型
    // 但在定义中使用该类型
    // 仅B对象可调用此函数,因仅B能构造tag
    void Set(int i, const tag &) { val = i; }
};

文件header_B.h

struct B {
    void Set(A& a, int i) { a.Set(i, Tag<B>{}); }
};

文件main.cpp

#include "header_A.h"
#include "header_B.h"

int main() {
    A a;
    B b;
    b.Set(a,42);
}

这段代码可正常编译运行,但存在以下疑问:

  • 这段代码是否符合C++标准?
  • 背后涉及哪些C++标准规则?
  • 尤其在B仍是不完整类型时,为何能编写using tag = Tag<B>;并在A的成员函数中使用该tag类型?我猜测是因为代码从未需要B的完整类型,但想了解具体原理。

解答

这段代码完全符合C++标准,核心依据以下几项标准规则:

  1. 模板参数允许不完整类型
    C++标准规定,若模板的定义不需要模板参数的完整类型,则可以用不完整类型作为模板实参。在Tag<B>中,Tag类仅将T(即B)声明为友元——友元声明只需要类型名称,无需类型完整;同时Tag的默认构造函数也不需要访问B的成员或知晓其大小。因此在header_A.h中声明using tag = Tag<B>时,B虽为不完整类型,但此时Tag<B>并未被实例化,仅作为类型别名存在。

  2. 成员函数的延迟实例化
    A::Set的参数是const tag&(即const Tag<B>&),但成员函数的内联定义不会在struct A定义时立即实例化Tag<B>的完整类型。只有当A::Set被实际调用(如B::Set中调用a.Set(i, Tag<B>{}))时,才会触发Tag<B>的实例化,而此时B已在header_B.h中完成完整定义,满足实例化的要求。

  3. 友元权限与类型依赖的配合
    Tag类的设计利用了友元的访问控制:只有B能构造Tag<B>对象,因此只有B可以调用A::Set(该函数要求Tag<B>类型的参数)。整个过程中,A的定义阶段仅需B的前向声明,因为此时不需要Tag<B>的完整定义,仅引入类型别名即可。

内容的提问来源于stack exchange,提问作者Oersted

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 17:10:17