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

为何std::from_range构造容器时,部分场景需用std::views::as_rvalue?

关于std::from_range构造中拷贝/移动行为的疑问

为什么在语句std::vector a{ std::from_range, std::vector{TA(1), TA(2), TA(3), TA(4), TA(5)} | std::views::as_rvalue }中,尽管右侧vector是右值,仍需使用std::views::as_rvalue来避免拷贝;而在语句std::vector a{ std::from_range, std::views::iota(1,6) | std::views::transform([](auto elem) {return TA(elem); }) }中,却无需使用std::views::as_rvalue?

注:已知最优写法为std::vector<TA> a{ std::from_range, std::views::iota(1,6) }(无拷贝移动),但希望了解std::from_range构造函数的工作机制。

代码示例

#include <iostream>
#include <vector>
#include <ranges>

class TA
{
private:
    int  value;
public:
    TA(int _value) : value(_value)
    {
        std::cout << "Constructor TA, value = " << value << '\n';
    }
    TA(const TA& a)
    {
        value = a.value;
        std::cout << "Copy constructor TA, value = " << value << '\n';
    }
    TA(TA&& a) noexcept
    {
        value = a.value;
        std::cout << "Move constructor TA, value = " << value << '\n';
    }
    ~TA()
    {
        std::cout << "Destructor TA, value = " << value << '\n';
    }
};

void rvalue_vector_test1()
{
    std::vector a{ std::from_range, std::vector{TA(1), TA(2), TA(3), TA(4), TA(5)} };
}

void rvalue_vector_test2()
{
    std::vector a{ std::from_range, std::vector{TA(1), TA(2), TA(3), TA(4), TA(5)} | std::views::as_rvalue };
}

void iota_test1()
{
    std::vector a{ std::from_range,
                   std::views::iota(1,6) |
                   std::views::transform([](auto elem) {return TA(elem); }) };
}


int main()
{
   rvalue_vector_test1();
   rvalue_vector_test2();
   iota_test1();
}

问题解答

核心逻辑:std::from_range的行为本质

std::from_range构造的核心是遍历传入的range,将每个元素构造到目标容器的内存中,具体调用拷贝还是移动构造,完全取决于遍历range时拿到的元素的值类别:

  • 拿到左值:调用拷贝构造
  • 拿到右值(纯右值或亡值):优先调用移动构造,甚至可能被编译器优化为直接构造(省略移动)

场景1:右值vector为什么需要as_rvalue?

std::vector{TA(1), ...}是临时右值容器,但它作为range被遍历时,迭代器解引用返回的是左值引用(TA&)——这是因为vector的迭代器不管容器本身是不是右值,解引用都是指向容器内部元素的左值引用(容器是右值不代表内部元素是右值)。

所以rvalue_vector_test1中,std::from_range遍历这个vector时,每次拿到的都是TA的左值,只能调用拷贝构造,导致大量拷贝。

而std::views::as_rvalue的作用是把原range的元素包装成右值引用视图:遍历这个视图时,迭代器解引用返回的是TA&&(亡值),此时std::from_range会调用移动构造,避免拷贝。


场景2:iota+transform视图为什么不需要as_rvalue?

std::views::transform返回的视图,其迭代器解引用时会执行传入的lambda表达式:return TA(elem);。这个lambda返回的是一个临时TA对象(纯右值),而非指向已有对象的引用。

当std::from_range遍历这个视图时,拿到的是纯右值形式的TA,编译器会自动将其移动到vector的内存中(甚至通过RVO优化,直接在vector的内存里构造TA,连移动都省略),所以不需要额外的as_rvalue。


补充:最优写法的原理

std::vector<TA> a{ std::from_range, std::views::iota(1,6) }之所以没有拷贝和移动,是因为std::from_range支持直接构造优化:它会直接用range中的元素值(这里是int类型的1~5),在vector的内存空间中调用TA的TA(int)构造函数,完全跳过中间的临时对象,是效率最高的构造方式。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:32:06