“const T& arg”与“T arg”

时间:2009-10-14 15:37:58

标签: c++

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

void myFunction (const int &myArgument);

void myFunction (int myArgument);

14 个答案:

答案 0 :(得分:36)

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

答案 1 :(得分:19)

他们做不同的事情。 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的另一个优点是,由于它是副本,被叫方不能恶意改变原始值。它可以像任何局部变量一样自由地改变变量来完成它的工作。

答案 2 :(得分:12)

取自Move constructors。我喜欢简单的规则

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

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

    double Cube(double value);
    
  3. 否则

    3.1。如果函数总是在其中复制其参数,则按值进行。

    3.2。如果函数永远不会复制其参数,请参考const。

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

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

答案 3 :(得分:7)

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

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

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

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

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

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

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

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

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

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


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

    Should all/most setter functions in C++11 be written as function templates accepting universal references?

答案 4 :(得分:5)

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

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

答案 5 :(得分:3)

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

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

答案 6 :(得分:2)

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

答案 7 :(得分:2)

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

在通用代码中,你应该写“T const&amp;”大部分时间都是安全的。您还可以使用boost's call traits来选择最有前途的参数传递类型。据我所知,它基本上使用ref-to-const作为类类型和标量类型的pass-by-value。

但是,在某些情况下,您可能希望按值接受参数,无论创建副本的费用是多少。请参阅Dave的文章"Want Speed? Use pass by value!"

答案 8 :(得分:1)

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

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

答案 9 :(得分:1)

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

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

答案 10 :(得分:0)

两者之间的区别对于整数而言并不是很重要。

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

在这两种情况下,您都会在来电者

中看到这一点
myFunct(item);

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

Pass by Reference / Value in C++

的类似问题有一个非常好的答案

答案 11 :(得分:0)

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

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

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

答案 12 :(得分:-1)

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

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

答案 13 :(得分:-1)

传递对象时出现问题。如果传递值,则将调用复制构造函数。如果你没有实现一个,那么该对象的浅表副本将被传递给该函数。

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

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