【问题标题】:Should I return reference to heap object or return value?我应该返回对堆对象的引用还是返回值?
【发布时间】:2017-12-10 09:48:15
【问题描述】:

我有这两个简单的功能。我认为func1 是一个很好的解决方案,因为您通过引用传递对象。我的教科书给出了func2 作为最佳解决方案的答案。这仅仅是因为您没有取消分配heapstr 吗?如果我在 main 中声明 heapstr 然后将其传递给函数以便之后删除它会怎样?

#include <iostream>

using namespace std;


string& func1(const string &str) {
    string* heapstr=new string();
    for (int i = 0; i < str.size(); ++i) {
        *heapstr += str[i];
    }
    return *heapstr;
}

string func2(const string &str) {
    string heapstr;
    for (int i = 0; i < str.size(); ++i) {
        heapstr += str[i];
    }
    return heapstr;
}

int main() {
    cout << func1("aaa") << endl;
    cout << func2("aaa") << endl;
}

【问题讨论】:

  • 价值!始终喜欢将值作为默认值!甚至没有谈论泄漏。
  • func2,更安全更快捷(stackoverflow.com/questions/161053/…
  • 如果你想使用一个普通的指针,再想一想,如果你真的有充分的理由这样做。

标签: c++ performance memory-management reference


【解决方案1】:

我应该返回对堆对象的引用还是返回值?

按值返回。

有很多原因,但没有一个与性能真正相关,因为编译器在优化事物方面足够好,即使不是,大多数程序都是 I/O 绑定的,即你的时间等待来自文件或网络套接字的数据会消耗您的所有性能,而不是 CPU 操作本身所花费的时间。

例如,参见 Herb Sutter 和 Bjarne Stroustrup 的“C++ 核心指南”,在"Return containers by value (relying on move or copy elision for efficiency)" 部分说:

原因

简化代码并消除显式内存管理的需要。

至于你的两个功能……

我的教科书给出了func2 作为最佳解决方案的答案。这仅仅是因为你没有释放heapstr吗?

内存泄漏是问题之一。但关键是按值返回更简单,更不容易出错。这完全是关于正确性,而不是速度。如果您可以只返回 int,您就不会返回 int*,对吗?

如果我在main 中声明heapstr,然后将其传递给函数,以便之后可以删除它会怎样?

您会在代码中引入很多内存泄漏、崩溃和未定义行为的可能性。它会变得更长、更难编写、更难阅读、更难维护、更难调试并且更难在代码审查中证明其合理性。作为回报,您将一无所获。

【讨论】:

  • 如果像你这样的人坚持写出比我更好的答案,我怎么能达到 10,000 次?
【解决方案2】:

教科书是正确的。 (震惊。)

Func1 在与 func2 不同的各个方面都有缺陷。它从堆中分配一个对象,而不考虑该对象将如何被删除。然后它返回一个对新对象的引用,隐藏了可能被用来删除它的指针。没有效率增益,实际上 Func1 可能有点慢。无论如何,跟着我背诵:“避免早期优化。”

自从标准模板库问世以来,许多个月前,几乎从来没有最好使用 operator new。我最后一次使用 operator new 是 ca。 2003 年,我将指针包装在我们现在所知的 unique_ptr 中。在使用 operator new 之前,请阅读有关了解智能指针和 RAII 的所有内容。

【讨论】:

    【解决方案3】:

    由于这是教科书示例,您应该考虑它的上下文以了解它想要准确显示的内容(它的目标是尽量减少内存使用或使用安全的编程模式?!)。但有两个提示

    1. 当您使用new 运算符分配内存时,您必须 使用delete 取消分配它。该代码在func1 处存在heapstr 的内存泄漏。
    2. 在更现实的项目中,在方法之间共享对象也不安全。它的管理(即当前谁修改了它,或者当不再需要对象时谁负责释放它的内存)变得困难。

    PS:我没有 C++17,但它也优化了以下内容。有关更多详细信息,请阅读@BoPersson cmets。

    PS:堆栈分配更快,但在您的示例中,您在 func2 返回时进行了复制操作。 在你的例子中,@Jive Dadson 说编译器优化没有区别,但在一般情况下,假设以下代码

    #include <iostream>
    #include <string>
    using namespace std;
    
    
    string& func1(const string &str) {
        string* heapstr = new string();
        cout << "func1 " << heapstr << endl;
        for (int i = 0; i < str.size(); ++i) {
            *heapstr += str[i];
        }
        return *heapstr;
    }
    
    string func2(const string &str) {
        string heapstr;
        for (int i = 0; i < str.size(); ++i) {
            heapstr += str[i];
        }
        cout << &heapstr << endl;
        return heapstr;
    }
    
    int main() {
        string a = func1("aaa");
        string b = func2("aaa");
        cout << "main " << a << endl;
    }
    

    PS:(正如@Jive Dadson 所说,您的示例没有区别,但在我的示例中)如果我们将性能定义为运行时,也许是 func1。此外,如果我们将性能定义为内存使用量,则 func1。如果我们将性能定义为良好的编程模式,func2。 Totally func2 更受欢迎。

    【讨论】:

    • 在 func1 中,堆上有一个指针和一个字符串。在 func2 你只有字符串。那是 less 内存。大多数编译器已经不会复制 func2 的返回值,而是直接将其创建到 b 中。在 C++17 中,此复制省略现在是强制性的。
    • @BoPersson ...已经是大多数编译器...我认为 C++17 是非常新的,可以算作大多数编译器。我用 VS17(它使用 11 或更高版本)检查它,但它不会这样做。关于第一部分,如果我们假设编译器对其进行了优化,那么您是对的。也感谢您的来信。我更新了我的答案。
    • 复制省略一直是被允许的,所以经常有效。最新版本的 g++ 和 clang 已经实现了 C++17,而 VS2017 刚刚在 preview of version 15.6 中实现了复制省略。
    • @BoPersson 你正处于 C++ 的边缘。对我来说,大多数编译器是指当今人们使用的 c++ 编译器的百分比。但是对于您(最新)类型的编译器。我把你的笔记放在 PS 上面,谢谢。
    猜你喜欢
    • 2011-02-25
    • 2010-12-10
    • 2018-08-20
    • 2014-09-26
    • 1970-01-01
    • 2012-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多