【问题标题】:Is it possible to return an a reference to the data in an std::vector, and preserve that reference after the vector goes out of scope?是否可以返回对 std::vector 中数据的引用,并在向量超出范围后保留该引用?
【发布时间】:2020-05-20 00:25:04
【问题描述】:

我习惯于用 C 编程,在那种语言中,我只会返回一个指向数据的指针,然后调用者将负责释放数据,但是,从我读过的内容来看,向量的析构函数将一旦超出范围就被调用,导致它的数据被释放。

一旦返回引用,内容的大小就不会改变,所以如果我可以有一个指向数据的指针,然后我可以手动删除,那将是理想的。我真正不想想要做的是将所有数据复制到一个新容器中,因为这个向量会变得非常大。

任何帮助将不胜感激。到目前为止,我看到的每个解决方案都涉及按值复制并依赖编译器对其进行优化,或者使用其他类来包装向量。

编辑:要清楚,我要保留的向量的唯一部分是指向其数据的指针(即您使用 vector.data() 方法获得的指针),我不需要保留任何原始向量的其他信息。

【问题讨论】:

  • 引用的全部意义在于它不拥有该对象。在这里使用std::unique_ptr 是完全有效的。
  • 不,尽管您可以查看智能指针。在 c++ 中,它会被复制,或者简单地作为对象返回(可能带有移动构造函数)。
  • std::vector 和标准库中的大多数容器都支持移动语义。如果您有类似 std::vector getData(...) { std::vector result; // 生成数据;返回结果; } 结果被移动而不是复制。这不是编译器优化。所以,someitmes 不需要返回一个指针。
  • @KennyOstrom 我认为这与我的问题不一样,因为在这种情况下它们返回一个向量,而在我的情况下,函数确实需要返回一个原始指针。

标签: c++ memory stdvector


【解决方案1】:

您描述的用例的良好(安全且快速)解决方案:不要返回引用,而是将向量返回到范围之外。

是否可以返回对 std::vector 中数据的引用,并在向量超出范围后保留该引用?

是的...如果您使用静态存储。具有静态存储持续时间的对象的生命周期会延长到程序结束。因此,当它们超出范围时,它们仍然活着。请注意,静态存储是全局状态,这通常是有问题的。尽可能避免使用并小心使用。

自动存储:否。


我只返回一个指向数据的指针,然后调用者负责释放数据

这是一个有问题的方法。调用者如何知道他们负责释放数据?调用者如何知道如何释放数据?调用者如何知道他们何时可以释放数据(如果有其他数据用户)?这一切都依赖于调用者阅读文档,理解它,并且不会出错。这是许多内存泄漏、通过无效指针访问和双重释放崩溃的根源。

std::vector 通过保持生命周期与容器对象相关联,解决了这些问题(在某种程度上;C 或 C++ 中没有任何接口如果努力就不会被滥用)。


如果我可以有一个指向数据的指针,然后我可以手动删除那将是理想的。

你不能拥有它。 std::vector 总是破坏它的数据,并且不可能将数据窃取到向量之外(通过移动或交换进入另一个向量除外)。

【讨论】:

  • 调用者知道要释放数据,因为调用者已经被写入期望这个函数会产生一个指针。数据本身只是一堆原语。你确定没有办法窃取数据吗?我不在乎原始向量之后是否无法使用,因为它会立即超出范围。
  • @TrevorGalivan The caller knows to free the data because the caller has already been written with the expectation that this function will produce a pointer. 并非所有指针都应该被调用者释放。示例:strtok Are you sure that there is no way to steal the data? 是。
  • 好的,谢谢。因此,如果我返回向量本身,数据最终会被深度复制吗?还是保证数据保持在原位,只复制向量的其他属性?
  • @TrevorGalivan will the data end up getting deep-copied? 不。如果要复制任何东西,那将是移动,即浅拷贝。在向量的情况下,涉及复制大约 24 个字节,其他一些设置为零。但几乎所有编译器都会忽略这一举动,因此根本不会复制任何内容。
【解决方案2】:

引用的全部意义在于它不拥有对象。在此处使用 std::unique_ptr 是完全有效的,它拥有该对象并可以返回给您的调用者。

您的函数将定义如下(我假设一个整数向量),以及 C++14 或更高版本:

std::unique_ptr<std::vector<int>> getVector() {
    auto vec = std::make_unique<std::vector<int>>(/*any ctor args you want*/);
    // for example
    vec->push_back(1);
    vec->push_back(2);
    return vec;
}

然后调用者可以执行以下操作:

int main() {
    auto vec = getVector();
    std::cout << vec->size() << std::endl;
}

当unique_ptr 超出范围时,该向量将被安全删除。请注意,如果您使用的是 C++11,则不会有 std::make_unique,需要执行以下操作:

std::unique_ptr<std::vector<int>> vec(new std::vector<int>(/* ctor args */));

【讨论】:

  • 虽然它在技术上可以满足 OP 的要求,但我认为没有充分的理由像这样动态分配向量。
【解决方案3】:

只需像这样编写代码:

std::vector<blah> my_function_that_returns_a_vector ()
{
    std::vector <blah> v;
    ... code to populate v ...
    return v;
}

NVRO 然后将删除副本。相反,返回的向量直接在调用者的堆栈帧中构造。

【讨论】:

  • 值得指出的是,NVRO 依赖于编译器和设置,因此无法保证。但是,如果您使用 C++11 或更高版本,move semantics 将接管以完成或多或少相同的工作:std::vector 有一个 move 构造函数——您只需要确保blah 类也是如此。
【解决方案4】:

在超出范围后无法保留对向量的引用,因为正如您所提到的,它将被删除。一旦向量被删除,存储在该地址的内存将无法访问。或者,您可以在范围之外声明变量,在范围内对其进行编辑。您提到通过引用返回 - 您是否可以通过引用将向量传递给函数,并简单地返回 void?这样,您就可以在不同的范围内编辑同一个向量。

【讨论】:

    猜你喜欢
    • 2015-03-21
    • 2013-03-20
    • 2011-08-30
    • 2019-10-27
    • 1970-01-01
    • 2011-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多