在 C++ 中返回假对象引用的规则

Posted

技术标签:

【中文标题】在 C++ 中返回假对象引用的规则【英文标题】:Rules for returning fake object reference in C++ 【发布时间】:2016-07-29 02:17:30 【问题描述】:

我想使用不拥有数据但作用于其中一部分的自定义容器来遍历预分配的浮点数组。示例,命名容器类LinhaSobre

std::unique_ptr<float[]> data(new float[720]);
...
//creates container to iterate 26 floats starting from from data[12]
    LinhaSobre cont(data.get()+12, 26); 
//sets those elements to 1.5
    for(size_t i = 0; i < cont.size(); i++)
        cont[i] = 1.5f;

这是operator[] 的可能实现:

//...
//LinhaSobre has a member mem0 which is initialized
//as a pointer to where the interval starts
float & LinhaSobre::operator[] (size_t i)

    return *(mem0+i);

请注意,我将来自LinhaSobre::operator[] 的引用返回到它不拥有的数据。它不应干扰数据的生命周期(构造函数、析构函数)。

现在我想通过另一种模式std::array&lt;float,4&gt; 公开存储的data,而不是纯float。示例,将新类命名为LinhaSobre4f

std::unique_ptr<float[]> data(new float[720]);
...
//creates container to iterate 4 array<float, 4> starting from from data[12]
    LinhaSobre4f l(data.get()+(3*4), 4);
//sets those elements to 1.5f, 2.5f, 3.5f, 4.5f;
    for(size_t i = 0; i < l.size(); i++)
        l[i] =  1.5f, 2.5f, 3.5f, 4.5f ;

请注意,我将这些项目视为一个数组。 这会导致容器类发生一些变化,我主要关心的是operator[],这是完整的类代码:

struct LinhaSobre4f

    LinhaSobre4f(float * pos_begin, size_t size_):
        pos0(pos_begin),
        size_(size_)
    std::array<float, 4> & operator[](size_t i)const
    
        std::array<float,4> * r = 
            reinterpret_cast<std::array<float,4>*> (pos0+(4*i));
        return *r;
    
    size_t size()const
    
        return size_;
    
private:
    float * pos0;
    size_t size_;
;

operator[] 返回一个对被视为 std::array&lt;float,4&gt; 的内存块的引用,该内存块从未真正存在过,但鉴于 std::array 内存布局保证,它可以工作。我对此表示怀疑,可以吗? (除了内存对齐,我会保证)。我是否可以在语义上公开这样的对象?什么是正确的术语? (我在标题中使用了 fake object)。

Here's a live demo of the example。 Here's another (the other link sometimes fails)

【问题讨论】:

对我来说绝对突出的一件事(编译器会警告您)是在LinhaSobre4foperator[] 中,您正在返回对临时变量的引用 - 您应该更改此方法的签名以按值而不是引用返回。 (我是吗?)本地(临时)变量是一个指针,我返回的是对它的取消引用,而不是对它的引用。 认为你很安全,虽然我没有什么可以支持的。 "例如,将容器类命名为 LinhaSobre" => Container 会不会更分散注意力? @Kahler 实际上我可能错了,指针应该是安全的。 【参考方案1】:

C++ 标准(我正在阅读 C++11)定义了一个std::array 如下:

应满足聚合条件 (8.5.1)。

您不能保证 std::array 是 POD。 C++ 标准只保证它是一个类聚合。

基于此,我相信您使用reinterpret_castfloats 的POD 数组转换为std::array 是未定义的行为。

它可能会与您的编译器一起工作,但您不能保证这将是可移植的或合法的。

【讨论】:

搜索了一下,我遇到了std::is_pod,如果it returns true to std::array<float, 4>,我可以假设它是安全的吗? 如果它在您当前的编译器中返回 true 是安全的。但是,请记住,如果它在您当前的编译器中返回 true,则无法保证它会在任何其他编译器或任何未来或以前版本的编译器中返回 true。【参考方案2】:

您可以创建一个普通的旧reference_type:

struct LinhaSobre4f 
    struct Ref 
        Ref(float *m): m(m);
        Ref &operator=(std::initializer_list<float> const &l) 
            std::copy(l.begin(), l.end(), m);
            return *this;
        
    private:
        float *m;
    ;
    Ref operator[](size_t i)  return m + 4 * i; 
private:
    float *m;
;

【讨论】:

我理解委托,但是 operator[] 现在返回一个值,而不是一个引用......我的愿望是能够直接在数据中操作,除了 Ref 现在必须实现每一个需要的操作。【参考方案3】:

加上 Sam Varshavchik 的回答,您可能对 span 类型 (formerly known as array_view) 感兴趣。

span 类型是一种抽象,它提供了对连续对象序列的视图,其存储由其他对象拥有(更多详细信息请参见 P0122R1、CppCoreGuidelines 和 Guidelines Support Library Review: span&lt;T&gt;)。

从概念上讲,span 只是一个指向某个存储的指针以及可通过该指针访问的元素的计数。小到可以传值。

https://github.com/Microsoft/GSL 上提供了一个开源(仅标头)参考实现(该实现通常假定平台支持 C++14。有特定的解决方法来支持 MSVC 2013 和 2015)。

【讨论】:

哇!这似乎完全符合我的情况!不过,它是一个参考类......不是直接访问。我越来越相信 C++ 语义不会直接涵盖 treat this chunk of memory as (...)。帮助类必须以某种方式对访问进行编码。

以上是关于在 C++ 中返回假对象引用的规则的主要内容,如果未能解决你的问题,请参考以下文章

C++基础问题 需返回局部对象的引用时的处理

如何在 C++ 中通过引用返回类对象?

在 C++ 中使用对引用的引用的目的是啥?

在 C++ 中使用引用的规则 [重复]

C++ 对象作为返回值:复制还是引用?

如何正确地从 C++ 中的迭代器返回对对象的引用