RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 760436
Accepted
αλεχολυτ
αλεχολυτ
Asked:2020-12-19 19:05:51 +0000 UTC2020-12-19 19:05:51 +0000 UTC 2020-12-19 19:05:51 +0000 UTC

按引用传递后跟复制与按值传递

  • 772

有些类型很难复制,例如:

struct S 
{ 
    int a[100]; 
};

任务是处理这种类型的变量的值并返回修改后的副本,即 必须保留原件。建议两种方法:

  1. S test(const S& s)
    {
        S news = s;           // делаем копию
        news.a[42] = 100500;  // изменяем 
        return news;          // возвращаем
    }
    
  2. S test(S s)               // делаем копию
    {
        s.a[42] = 100500;     // изменяем
        return s;             // возвращаем
    }
    

选项 2 在代码方面看起来更短,但是,如汇编所示,按引用传递会导致汇编代码更短。

为什么会发生这种情况?这两种方法的优缺点是什么,以便了解更喜欢哪一种?也许还有其他选择?

c++
  • 5 5 个回答
  • 10 Views

5 个回答

  • Voted
  1. Best Answer
    AnT stands with Russia
    2020-12-20T04:01:41Z2020-12-20T04:01:41Z

    在现代 C++ 中,第二个选项被认为更可取。那些。如果你知道无论如何你都需要一个副本,最好让编译器为你制作那个副本,而不是你自己。

    然而,这个断言的传统理由依赖于“难以”复制的类型,不是因为它们本身很大,而是因为它们需要深度复制。那些。我们谈论的是在浅(浅)级别紧凑但通过指针/句柄拥有额外资源的类型。这里的整个想法是,在原始值是临时/可重定位的情况下,编译器将能够用移动替换复制。

    例如,如果在您的情况下将其替换S为std::string,则在调用test("abc")第二个选项时,在准备参数时,它将完全不进行深度复制,而在第一个选项中,您自己将无条件地执行深度复制。

    const std::string &(带有参数和参数的两个单独函数的选项可能更有效std::string &&,但如果您不想压缩最后的处理器周期,那么带有参数的单个函数std::string通常看起来更有吸引力。)

    在对象的“重量”直接构建到对象本身的情况下,如您的示例中,将无法节省复制。我希望这两个选项具有相同的性能。

    一些细微差别是,根据语言的抽象语义,在第二个变体中创建副本是在调用代码的上下文中完成的。一些实现从字面上遵循这种语义——它们在调用代码的上下文中为其执行副本创建和内存分配。在这种情况下,可以提前保留内存,而不管函数在执行期间是否会被实际调用。那些。比如说,写这样一个递归函数

    void recursive(unsigned n, const S &s)
    {
      if (n > 0)
        recursive(n - 1, s);
      else
        test(s);
    }
    

    您可能会惊讶地发现,在使用函数的第二个版本时,在每个递归级别都分配了test用于复制的堆栈空间,而实际上只有在递归的最底部才需要此内存。第一个选项将没有这个缺点。stest

    其他实现可能更经济:即使使用第二个选项test,仅在实际调用函数时才保留内存。

    • 7
  2. yrHeTateJlb
    2020-12-19T19:35:45Z2020-12-19T19:35:45Z

    引用 McConnell 的完美代码,第 7.5 章:

    不要将方法参数用作工作变量

    使用传递给方法的参数作为工作变量是危险的。为此目的创建局部变量。因此,在以下 Java 代码片段中,inputVal 变量被错误地用于存储计算的中间结果:

    int Sample(int inputVal) {
        inputVal = inputVal * CurrentMultiplier( inputVal );
        inputVal = inputVal + CurrentAdder( inputVal );
    
        //...
    
        //Переменная inputVal уже не содержит входного значения. 
        return inputVal; 
    } 
    

    在这个片段中,变量 inputVal 具有误导性,因为当方法结束时,它不再包含输入值;它包含部分基于输入值的计算结果,因此它的名字是不幸的。如果您以后需要在方法的其他地方使用原始输入值,您可能会使用 inputVal 变量,假设它包含原始值,但这种假设是错误的。

    恕我直言:这是错误的余地。虽然他自己写道:

    template<class T>
    QVector<T> reversed(QVector<T> vector){
        std::reverse(vector.begin(), end.begin());
        return vector;
    }
    

    ...并且不会感到内疚:)

    根据 McConnell 的推理,通过引用传递参数并制作本地副本更有意义。

    • 3
  3. ixSci
    2020-12-19T21:23:05Z2020-12-19T21:23:05Z

    第一个选项显然更好,因为它符合多年来推广的良好 C++ 代码的概念。这样的代码不会引起任何问题,但不是通过引用传递的结构会引起问题。而且结构的规模越大,这样的决定就会引发更多的问题。如果它提出问题,那么它需要评论。另外,正如您自己在问题中指出的那样,您没有衡量这两种方法的有效性(汇编程序列表的大小根本没有任何意义),因此没有什么可参考的。

    在我看来,这个问题是尝试过早优化的典型示例,通常会导致悲观情绪,对代码可读性产生负面影响。


    关于这个主题写了很长的文字。可以在此链接中找到:通过引用传递还是按值传递?

    • 3
  4. MSDN.WhiteKnight
    2020-12-19T23:35:49Z2020-12-19T23:35:49Z

    通过引用传递给出更短的汇编代码

    在您链接到的 x86-64 clang 汇编器中,第一个和第二个选项之间的区别是:

    在第一个变体中,优化被触发,结构从作为参数传递的地址立即复制到返回值的位置。退货不需要任何额外的步骤。

    在第二个变体中,没有应用这种优化,结构被复制了两次。第一次调用代码创建一个副本以传递给函数(在给定代码的括号之外),第二次从函数返回时。

    第二个选项由于 operator 的原因而长了一行,它只lea rsi, [rsp + 16]计算memcpy调用的源参数(在第一种情况下,这不是必需的,因为它已显式传递给调用函数)。因此,由于结构的双重复制和memcpy的参数的额外计算,第二个选项在真空中呈球形,效率不高。

    这两种方法的优缺点是什么,以了解更喜欢哪一种?

    我想在真正的程序中你不必担心这个。优化编译器不会考虑单个函数,而是将程序作为一个整体来考虑,并会选择最佳选项。尽管第二个选项理论上“更糟”,但实际上编译器只会将两个函数都变成内联函数(如果它们不带指针),并且不会有任何区别。

    • 3
  5. user278482
    2020-12-19T20:11:41Z2020-12-19T20:11:41Z

    作为一个合适的选择

    S* test(const S& s)
    {
    S *news = new S;         // создаём новый элемент
    *news = s;              // делаем копию
    news->a[42] = 100500;  // изменяем 
    return news;          // возвращаем
    }
    
    • -3

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5