繁体   English   中英

“const T&arg”与“T arg”

[英]“const T &arg” vs. “T arg”

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

void myFunction (const int &myArgument);

要么

void myFunction (int myArgument);

如果sizeof(T)>sizeof(void*) ,则使用const T & arg ,如果sizeof(T) <= sizeof(void*) ,则使用T arg

他们做不同的事情。 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;
}

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

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

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

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

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

     double Cube(double value); 
  3. 除此以外

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

通常对于内置类型,您只需按值传递即可。 他们是小型的。

对于用户定义的类型(或模板,当你没有要传递的东西时)更喜欢const&。 引用的大小可能小于类型的大小。 并且它不会产生额外的副本(不调用复制构造函数)。

嗯,是的......关于效率的其他答案都是正确的。 但是这里还有其他一些重要的事情 - 按值传递一个类会创建一个副本,因此会调用复制构造函数。 如果你在那里做过花哨的东西,那么使用引用是另一个原因。

对于标量类型(如int,double等),对const T的引用不值得输入。经验法则是应该通过ref-to-const接受类类型。 但对于迭代器(可能是类类型),我们经常会例外。

在通用代码中,您应该在大多数情况下写“T const&”以保证安全。 还可以使用boost的调用特性来选择最有前途的参数传递类型。 据我所知,它基本上使用ref-to-const作为类类型,并使用pass-by-value作为标量类型。

但是,在某些情况下,您可能希望按值接受参数,而不管创建副本的成本是多少。 请参阅Dave的文章“想要速度?使用价值传递!”

对于像int,double和char *这样的简单类型,按值传递它是有意义的。 对于更复杂的类型,我使用const T&除非有特殊原因不这样做。

传递4 - 8字节参数的成本与您可以获得的一样低。 您不通过传递参考购买任何东西。 对于较大的类型,按值传递它们可能很昂贵。

对于int来说它没有任何区别,因为当你使用引用时,仍然必须传递内存地址,并且内存地址(void *)通常大约是整数的大小。

对于包含大量数据的类型,它变得更加高效,因为它避免了必须复制数据的巨大开销。

那么两者之间的差异对于整数来说并不是很重要。

但是,当使用较大的结构(或对象)时,您使用的第一个方法(通过const引用)可以访问该结构而无需复制它。 第二种情况传递值将实例化一个与参数具有相同值的新结构。

在这两种情况下,您都会在调用者中看到此信息

myFunct(item);

对于调用者,myFunct不会更改项目,但是按引用传递不会产生创建副本的成本。

在C ++中通过引用/值的类似问题有一个非常好的答案

它们之间的区别在于,传递一个int(被复制),一个使用现有的int。 因为它是一个const引用,所以它不会被改变,所以它的工作方式大致相同。 这里最大的区别是函数可以在本地改变int的值,但不能改变const引用。 (我想有些白痴可以用const_cast<>做同样的事情,或者至少尝试一下。)对于较大的对象,我可以想到两个不同之处。

首先,一些对象根本无法复制, auto_ptr<>和包含它们的对象就是一个明显的例子。

其次,对于大而复杂的对象,通过const引用传递比复制更快。 它通常不是什么大问题,但通过const引用传递对象是一个有用的习惯。

要么工作正常。 不要浪费你的时间担心这些东西。

唯一可能产生影响的是当类型是一个大型结构时,传递堆栈可能会很昂贵。 在这种情况下,将arg作为指针或引用传递(稍微)更有效。

传递对象时会出现问题。 如果传递值,则将调用复制构造函数。 如果尚未实现,则将该对象的浅表副本传递给该函数。

为什么这是个问题? 如果您有指向动态分配内存的指针,则可以在调用副本的析构函数时释放(当对象离开函数的作用域时)。 然后,当你重新打电话给你的析构函数时,你将获得双倍免费。

道德:写你的副本构造函数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM