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

如何使自定义类直接作为std::range使用?解决视图临时对象悬垂问题

让自定义类直接作为Range使用的解决方案

问题背景

现有一个类BuildAView,通过非trivial的初始化操作后,基于输入容器生成std::views::transform视图,当前需调用getView()才能遍历:

class BuildAView
{
public:
    BuildAView(std::array<float, 5>& in)
        : mIn{in}
    {
        // 执行非trivial的初始化操作
    }

    auto getView()
    {
        return mIn | std::views::transform([](float x){ return doSomething(x); });
    }
    
private:
    std::array<float, 5>& mIn;
};

使用方式为:

for (auto&& x : buildAView.getView()) { ... }

期望实现直接遍历类对象:

for (auto&& x : buildAView) { ... }

但直接在类内实现begin()/end()会导致悬垂临时对象:

class BuildAView
{
    // ... 其他成员同上
    auto begin()
    {
        // 临时视图表达式结束后销毁,返回的迭代器悬垂
        return (mIn | std::views::transform([](float x){ return doSomething(x); })).begin();
    }
    auto end()
    {
        // 同理,临时视图销毁,迭代器悬垂
        return (mIn | std::views::transform([](float x){ return doSomething(x); })).end();
    }
};

已尝试的方案:

  • 在构造函数中构建视图并存储,但视图类型过于复杂无法手动声明,导致成员变量类型定义困难
  • 全局begin()/end()函数无法解决临时对象悬垂问题,因为没有合适的存储位置

解决方案

方案1:C++20 类内auto成员存储视图

C++20允许类成员变量使用auto声明,只要在构造函数中完成初始化,编译器会自动推导其类型。利用这一特性,可将视图直接存储为类成员,避免临时对象悬垂:

class BuildAView
{
public:
    BuildAView(std::array<float, 5>& in)
        : mIn{in},
          mView(mIn | std::views::transform([](float x){ return doSomething(x); }))
    {
        // 执行你的非trivial初始化操作
    }

    // 直接返回存储视图的迭代器
    auto begin() { return mView.begin(); }
    auto end() { return mView.end(); }

private:
    std::array<float, 5>& mIn;
    auto mView; // C++20 自动推导视图类型
};

实现后即可直接用for (auto&& x : buildAView)遍历,完全符合预期。

方案2:C++17 兼容方案(类型擦除)

如果受限于C++17,无法使用类内auto成员,可通过std::any进行类型擦除(存在轻微性能开销):

#include <any>
#include <ranges>
#include <functional>

class BuildAView
{
public:
    BuildAView(std::array<float, 5>& in)
        : mIn{in}
    {
        auto view = mIn | std::views::transform([](float x){ return doSomething(x); });
        mView = std::move(view);
        // 存储迭代器的获取逻辑
        mBegin = [this]() { return std::any_cast<decltype(view)>(&mView)->begin(); };
        mEnd = [this]() { return std::any_cast<decltype(view)>(&mView)->end(); };
    }

    auto begin() { return mBegin(); }
    auto end() { return mEnd(); }

private:
    std::array<float, 5>& mIn;
    std::any mView;
    std::function<decltype(std::declval<decltype(mIn | std::views::transform(std::declval<decltype([](float x){return doSomething(x);})>()))>().begin())()> mBegin;
    std::function<decltype(std::declval<decltype(mIn | std::views::transform(std::declval<decltype([](float x){return doSomething(x);})>()))>().end())()> mEnd;
};

该方案代码冗余,性能不如C++20的直接存储方式,优先推荐前者。

核心原理

之前的实现失败是因为每次调用begin()/end()都会生成临时视图,临时对象在表达式结束后立即销毁,导致返回的迭代器指向无效内存(悬垂)。将视图存储为类成员后,视图的生命周期与类对象一致,迭代器始终有效,同时解决了迭代器类型不一致的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 20:21:09