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

C++中operator->如何保持值类别?是否存在类型系统漏洞?

C++中operator->的值类别转发问题

假设有结构体Bar和Foo,其中Foo重载的operator*可以在转发成员访问时保持值类别:

#include <cstdio>

struct Bar {
    void func() & {
        printf("func() &\n");
    }
    void func() && {
        printf("func() &&\n");
    }
};

struct Foo {
    Bar bar;

    Bar &operator*() & {
        return bar;
    }
    Bar &&operator*() && {
        return static_cast<Bar &&>(bar);
    }
};

int main() {
    Foo foo;

    (*foo).func();   // 输出 func() &
    (*Foo()).func(); // 输出 func() &&
}

为简化func()调用,给Foo添加operator->:

struct Foo {
    Bar bar;

    Bar &operator*() & {
        return bar;
    }
    Bar &&operator*() && {
        return static_cast<Bar &&>(bar);
    }

    Bar *operator->() & {
        return &bar;
    }

    // 本应返回“指向右值Bar的指针”,但C++无此概念
    Bar *operator->() && {
        return &bar;
    }
};

但C++中不存在左值/右值指针的概念,导致以下代码均调用左值版本func() &:

int main() {
    Foo foo;

    foo->func();   // 输出 func() &
    Foo()->func(); // 输出 func() &
}

针对这一现象,提出两个技术问题:

  1. 能否让operator->像operator*一样保持值类别,同时维持现有调用语法?
  2. 该现象是否属于C++类型系统的漏洞?

问题1解答:可以通过代理对象实现

要让operator->保持值类别转发且不修改调用语法,核心思路是让operator->返回代理对象而非原生指针,利用代理对象的引用限定符(&/&&)来控制最终成员访问的表达式值类别。

具体实现如下:

#include <cstdio>
#include <utility> // for std::move

struct Bar {
    void func() & {
        printf("func() &\n");
    }
    void func() && {
        printf("func() &&\n");
    }
};

struct Foo {
    Bar bar;

    Bar &operator*() & {
        return bar;
    }
    Bar &&operator*() && {
        return std::move(bar);
    }

    // 代理类:用于转发值类别
    struct ArrowProxy {
        Bar& bar_ref;

        // 递归返回自身,触发operator*的调用
        ArrowProxy* operator->() {
            return this;
        }

        // 根据代理对象的值类别返回对应引用
        Bar& operator*() & {
            return bar_ref;
        }
        Bar&& operator*() && {
            return std::move(bar_ref);
        }
    };

    // 左值Foo返回左值代理
    ArrowProxy operator->() & {
        return {bar};
    }
    // 右值Foo返回右值代理
    ArrowProxy operator->() && {
        return {bar};
    }
};

int main() {
    Foo foo;
    foo->func();   // 输出 func() &
    Foo()->func(); // 输出 func() &&
}

原理说明

C++中E->M的语义是递归调用operator->直到得到指针,最终等价于(*X).M(X是最终的指针或代理对象)。上述实现中:

  • 左值Foo调用operator->返回左值ArrowProxy,最终调用operator*&返回Bar&,触发左值版本func();
  • 右值Foo调用operator->返回右值ArrowProxy,最终调用operator*&&返回Bar&&,触发右值版本func()。

整个过程完全保留foo->func()的调用语法,同时实现了值类别转发。


问题2解答:不属于类型系统漏洞,是设计权衡的结果

该现象并非C++类型系统的漏洞,而是标准委员会的设计取舍:

  1. 原生指针的语义一致性:C++的指针核心语义是“指向内存中的对象”,通过指针访问成员时,*指针的结果始终是左值——这是为了与原生指针的行为保持一致,避免引入“左值指针/右值指针”这类复杂概念,降低语言学习和实现成本。
  2. 明确的替代方案:标准库提供了operator*作为值类别转发的明确途径,而代理模式也能在需要时扩展operator->的能力,无需修改语言核心类型系统。

类似std::optional的容器也采用相同的operator->行为,这是为了维持与原生指针的行为对齐,保证API的一致性和可预测性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 05:50:20