"const T&arg"与"T arg"

Eta*_*tan 17 c++

以下哪个示例是声明以下功能的更好方法?为什么?

void myFunction (const int &myArgument);
Run Code Online (Sandbox Code Playgroud)

要么

void myFunction (int myArgument);
Run Code Online (Sandbox Code Playgroud)

Ale*_*tov 38

使用const T & argif sizeof(T)>sizeof(void*)和use T argifsizeof(T) <= sizeof(void*)

  • 这是误导IMO.我不会选择`const double&t`而不是`double t`但你的声明建议在32位系统上这样做. (12认同)
  • @Alexey,所有基本类型都应该是这个规则的例外,而不仅仅是`double`. (8认同)
  • 是的,也许,double是一个例外. (6认同)
  • 如果我的类只存储一个微小的数据成员但在其复制构造函数中执行一些代价高昂的操作,该怎么办?或者哪个没有复制构造函数?我认为这个经验法则没有任何意义 (5认同)
  • 那么,"长双"怎么样?`long long`(或`__int64`,或者你的实现的64位整数类型是什么)?成员指针(通常由3个机器字组成)?迭代器?分配器?它比这更复杂...... (4认同)
  • 那些你不知道T大小的模板怎么样! (2认同)

Meh*_*ari 20

他们做不同的事情.const T&使该函数接受对变量的引用.另一方面,T arg 将调用对象的复制构造函数并传递副本.如果复制构造函数不可访问(例如它private),T arg将无法工作:

class Demo {
    public: Demo() {} 
    private: Demo(const Demo& t) { } 
};

void foo(Demo t) { }

int main() {
    Demo t;
    foo(t); // error: cannot copy `t`.
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

对于像原始类型这样的小(其中所有重要的是对象的内容,而不是实际的引用标识;比如,它不是句柄或其他东西),T arg通常是首选.对于无法复制和/或保留引用标识的大对象和对象很重要(无论大小如何),首选传递引用.

另一个优点T arg是,由于它是副本,被调用者不能恶意改变原始值.它可以像任何局部变量一样自由地改变变量来完成它的工作.


Joh*_*itb 14

取自Move构造函数.我喜欢简单的规则

  1. 如果函数打算将参数更改为副作用,则通过引用/指针将其作为非const对象.例:

    void Transmogrify(Widget& toChange);
    void Increment(int* pToBump);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果函数不修改其参数且参数是基本类型,则按值取值.例:

    double Cube(double value);
    
    Run Code Online (Sandbox Code Playgroud)
  3. 除此以外

    3.1.如果函数始终在其中复制其参数,请按值取值.

    3.2.如果函数从不复制其参数,则通过引用const来获取它.

    3.3.我添加:如果该功能有时复制,那么决定直觉:如果副本几乎总是完成,那么按值进行.如果复制完成了一半的时间,那么请以安全的方式参考const.

在您的情况下,您应该通过值获取int,因为您不打算修改参数,并且参数是基本类型.我认为"原始类型"可以是非类型类型,也可以是没有用户定义的复制构造函数的类型,其中sizeof(T)只有几个字节.


AnT*_*AnT 7

有一个流行的建议,指出应该根据你要传递的类型的实际大小来选择传递方法("按值"与"通过const引用").即使在这个讨论中,你也有一个标记为"正确"的答案.

实际上,根据类型的大小做出决定不仅是不正确的,这是一个 重大且相当明显的设计错误,表明严重缺乏对良好编程实践的直觉/理解.

基于对象的实际依赖于实现的物理大小的决策必须尽可能多地留给编译器.尝试通过对传递方法进行硬编码来"调整"代码到这些大小是对100个中的99个案例的完全适得其反的浪费.(是的,确实如此,在C++语言的情况下,编译器不会有足够的自由可以互换地使用这些方法 - 在一般情况下它们在C++中并不是真的可以互换.虽然如果有必要,可以通过模板元编程实现适当的基于大小的[半]自动传递方法选择;但这是一个不同的故事).

在"手动"编写代码时选择传递方法的更有意义的标准可能听起来如下:

  1. 当您传递一个原子的,单一的,不可分割的实体时,更喜欢传递"by value",例如任何类型的单个非聚合值 - 数字,指针,迭代器.请注意,例如,迭代器是逻辑级别的单一值.因此,不管它们的实际大小是否大于sizeof(void*),都希望按值传递迭代器.(STL实现就是这样,BTW).

  2. 当您传递任何类型的聚合值时,更喜欢传递"by const reference".即在逻辑级别上暴露出明显"复合"性质的值,即使其大小不大于sizeof(void*).

两者之间的分离并不总是很清楚,但是所有这些建议总是如此.此外,分离成"原子"和"复合"实体可能取决于您的设计的具体情况,因此决策实际上可能因设计而异.

请注意,此规则可能会产生与本讨论中提到的所谓"正确"基于大小的方法不同的决策.

作为一个例子,它倾向于观察,基于大小的方法将建议您手动硬编码不同类型的迭代器的不同传递方法,具体取决于它们的物理大小.这使得基于大小的方法是多么虚伪是特别明显的.

再一次,良好的编程实践所依据的基本原则之一是避免将您的决策建立在平台的物理特性上(尽可能多).相反,您的决策必须基于程序中实体的逻辑和概念属性(尽可能多).传递"按价值"或"按参考"的问题在这里也不例外.


在C++ 11中,将移动语义引入语言产生了不同参数传递方法的相对优先级的显着转变.在某些情况下,按值传递复杂对象可能变得完全可行

是否应将C++ 11中的所有/大多数setter函数编写为接受通用引用的函数模板?

  • @Richard Corden:他们是否缺乏这种理解是完全无关紧要的,因为我确信这不是一个优先事项.他们使用的推理很可能与任何"良好的编程实践"完全正交.他们的标准的目的是在高度特定(或者应该说是异国情调的)环境中充分利用严格定义的工具集,这与一般的应用程序级C++编程和良好实践几乎没有关系.在那里申请. (2认同)

Jer*_*fin 5

与流行的和长期存在的信念相反,即使你传递一个大型物体,传递const引用也不一定更快.您可能想阅读Dave Abrahams最近 关于这个主题的文章.

编辑:(主要是回应Jeff Hardy的评论):在最大数量的情况下,通过const引用传递可能是"最安全"的选择 - 但这并不意味着它总是最好的事情.但是,为了理解这里讨论的是什么,你真的需要仔细阅读Dave的整篇文章,因为它相当技术性,其结论背后的推理并不总是直观明显(你需要理解做出明智选择的理由) ).

  • 是的,当然它有局限性 - 这就是为什么1)我说"必然",2)将人们引用到文章中以获取全部细节...... (2认同)
  • @sbi:你显然没有读完整篇文章.它不是严格意义上的返回对象 - 这只是他用作初始示例的内容. (2认同)