6

当 C++(隐式或显式)值构造函数需要以任何一种方式在其对象中存储参数的副本时,是否应该通过值或引用到常量来接受其参数?

这是我能想到的最短的例子:

struct foo {
    bar _b;
    foo(bar [const&] b) // pass by value or reference-to-const?
        : _b(b) { }
};

这里的想法是,我想在创建 foo 对象时以任何可能创建 foo 对象的各种方式来最小化对 bar 的复制构造函数的调用。

请注意,我确实对复制省略和(命名的)返回值优化有所了解,并且我已经阅读了“想要速度?按值传递”,但是我认为这篇文章没有直接解决这个用例。

编辑:我应该更具体。

假设我不知道sizeof(bar),或者是否bar是基本的内置类型(bar可能是模板参数,并且foo可能是类模板而不是类)。此外,不要假设foo' 构造函数可以内联(或bar' ,就此而言)。请假设我至少可能正在使用实现 RVO 的编译器。

我想要的是有可能(给定编译器优化)这样的调用不会调用任何对bar' 的复制构造函数的调用(即使_b(b)foo' 的初始化列表中执行时):

foo f = function_that_creates_and_returns_a_bar_object_using_rvo();

foo是否有可能(给定 C++98 标准)可以做到这一点,如果是这样,如果通过对 const 的引用而不是通过值接受其参数,它或多或少可能会起作用?

4

8 回答 8

5

在所有条件相同的情况下,对于足够复杂的类,我通过 const& 为 POD 和简单对象传递值。

列出通过 const 引用而不是传统的按值传递的优缺点

正面:

  • 避免复制(对于具有昂贵副本的对象来说是一大优势)
  • 只读访问

负面:

  • 如果他们真的想的话,有人可以 const 将 const 从引用中去掉

更重要的是,您可以明确控制复制发生的时间(在您的情况下,在初始化列表中传递初始化 _b 时)。考虑负面因素……我同意这是一种风险。我认为几乎所有优秀的程序员都会对使用 const_cast 感到肮脏。此外,您可以尽职尽责地搜索 const_cast 并将西红柿扔向从参数中抛出 const 的人。但是,嘿,你永远不知道,谁有时间像鹰一样看代码 :)?

我的主观意见是,在足够复杂的类和性能很重要的环境中,避免复制构造函数的好处大于风险。然而,对于真正愚蠢的 POD 类,我倾向于复制数据并按值传递。

于 2009-12-04T15:59:35.727 回答
4

看这个问题

使用const T & argifsizeof(T)>sizeof(void*)和使用T argif sizeof(T) <= sizeof(void*)。所有基本类型都应该是这个规则的例外

于 2009-12-04T15:57:50.537 回答
2

从风格上讲,我会说通过引用传递是更好的方法。

如果性能真的很重要,那么不要猜测。测量它。

于 2009-12-04T16:20:36.523 回答
2

我还没有检查标准的内容,但是通过经验性的尝试,我可以告诉你 GCC 不会优化副本,无论构造函数是通过值还是通过 const 引用接受参数。

但是,如果构造函数采用 const 引用,则当您从现有bar对象创建foo时,它会设法避免不必要的复制。

总结一下:

Bar b = makeBar();         // No copy, because of RVO
FooByValue f1 = makeBar(); // Copy constructor of Bar called once
FooByRef f2 = makeBar();   // Copy constructor of Bar called once
FooByValue f3(b);          // Copy constructor of Bar called twice
FooByRef f4(b);            // Copy constructor of Bar called only once

并不是说我是编译器专家,但我想它通常不能将返回值 RVO 到任意位置(例如foo对象的成员字段)是有道理的。相反,它要求目标位于堆栈顶部。

于 2009-12-04T19:41:08.340 回答
2

在 C++98 和 C++03 中,你应该传递const& bar然后复制。在 C++0x 中,您应该传递bar然后执行移动(前提bar是具有移动构造函数)。

#include <utility>

struct foo
{
    bar _b;

    foo(bar b) : _b(std::move(b)) {}
};

如果您使用左值参数构造 foo,则将调用复制构造函数来创建一个副本b,并且该副本将被移动到_b. 如果用右值参数构造 foo ,bar则会调用 ' 的 move 构造函数移入b,然后再次移入_b

于 2009-12-04T19:55:34.223 回答
1

我假设bar有一个普通的复制构造函数bar(const bar &b)

在此处获取 const 参考。bar的构造函数然后将获取该引用,并执行复制。总份数:1。

如果您关闭引用,编译器将制作 b 的副本,将副本传递给foo的构造函数,然后将其传递给bar并复制它。

于 2009-12-04T15:58:21.863 回答
0

老实说,对于这种情况,最好的选择是让你的构造函数inline,只要bar构造函数不抛出。然后通过引用传递。

此外,正如您所怀疑的,您的文章不适用于此处。

于 2009-12-04T16:02:48.467 回答
-1

在你的班级中持有一个 shared_pointer 并传递它。这样你就永远不会调用复制构造函数:)。

于 2009-12-04T15:57:16.607 回答