简体   繁体   English

“const T&arg”与“T arg”

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

Which of the following examples is the better way of declaring the following function and why? 以下哪个例子是声明以下功能的更好方法?为什么?

void myFunction (const int &myArgument);

or 要么

void myFunction (int myArgument);

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

They do different things. 他们做不同的事情。 const T& makes the function take a reference to the variable. const T&使函数引用变量。 On the other hand, T arg will call the copy constructor of the object and passes the copy . 另一方面, T arg 将调用对象的复制构造函数并传递副本 If the copy constructor is not accessible (eg it's private ), T arg won't work: 如果复制构造函数不可访问(例如它是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;
}

For small values like primitive types (where all matters is the contents of the object, not the actual referential identity; say, it's not a handle or something), T arg is generally preferred. 对于像原始类型这样的小 (其中所有重要的是对象的内容,而不是实际的引用标识;比如,它不是句柄或其他东西),通常首选T arg For large objects and objects that you can't copy and/or preserving referential identity is important (regardless of the size), passing the reference is preferred. 对于无法复制和/或保留引用标识的大对象和对象很重要(无论大小如何),首选传递引用。

Another advantage of T arg is that since it's a copy, the callee cannot maliciously alter the original value. T arg另一个优点是,由于它是副本,被调用者不能恶意改变原始值。 It can freely mutate the variable like any local variables to do its work. 它可以像任何局部变量一样自由地改变变量来完成它的工作。

Taken from Move constructors . 取自Move构造函数 I like the easy rules 我喜欢简单的规则

  1. If the function intends to change the argument as a side effect, take it by reference/pointer to a non-const object. 如果函数打算将参数更改为副作用,则通过引用/指针将其作为非const对象。 Example: 例:

     void Transmogrify(Widget& toChange); void Increment(int* pToBump); 
  2. If the function doesn't modify its argument and the argument is of primitive type, take it by value. 如果函数不修改其参数且参数是基本类型,则按值取值。 Example: 例:

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

    3.1. 3.1。 If the function always makes a copy of its argument inside, take it by value. 如果函数始终在其中复制其参数,请按值取值。

    3.2. 3.2。 If the function never makes a copy of its argument, take it by reference to const. 如果函数从不复制其参数,则通过引用const来获取它。

    3.3. 3.3。 Added by me : If the function sometimes makes a copy, then decide on gut feeling: If the copy is done almost always, then take by value. 我添加 :如果该功能有时复制,那么决定直觉:如果副本几乎总是完成,那么按值进行。 If the copy is done half of the time, go the safe way and take by reference to const. 如果复制完成了一半的时间,那么请以安全的方式参考const。

In your case, you should take the int by value, because you don't intend to modify the argument, and the argument is of primitive type. 在您的情况下,您应该通过值获取int,因为您不打算修改参数,并且参数是基本类型。 I think of "primitive type" as either a non-class type or a type without a user defined copy constructor and where sizeof(T) is only a couple of bytes. 我认为“原始类型”可以是非类型类型,也可以是没有用户定义的复制构造函数的类型,其中sizeof(T)只有几个字节。

There's a popular advice that states that the method of passing ("by value" vs "by const reference") should be chosen depending in the actual size of the type you are going to pass. 有一个流行的建议,声明应该根据你要传递的类型的实际大小来选择传递方法(“按值”与“通过const引用”)。 Even in this discussion you have an answer labeled as "correct" that suggests exactly that. 即使在这个讨论中,你也有一个标记为“正确”的答案。

In reality, basing your decision on the size of the type is not only incorrect, this is a major and rather blatant design error, revealing a serious lack of intuition/understanding of good programming practices. 实际上,根据类型的大小做出决定不仅是不正确的,这是一个主要且相当明显的设计错误,表明严重缺乏对良好编程实践的直觉/理解。

Decisions based on the actual implementation-dependent physical sizes of the objects must be left to the compiler as often as possible. 基于对象的实际依赖于实现的物理大小的决策必须尽可能多地留给编译器。 Trying to "tailor" your code to these sizes by hard-coding the passing method is a completely counterproductive waste of effort in 99 cases out of 100. (Yes, it is true, that in case of C++ language, the compiler doesn't have enough freedom to use these methods interchangeably - they are not really interchangeable in C++ in general case. Although, if necessary, a proper size-based [semi-]automatic passing methios selection might be implemented through template metaprogramming; but that's a different story). 试图通过对传递方法进行硬编码来“调整”代码到这些大小,这对于100个中的99个案例来说是完全适得其反的浪费。(是的,确实如此,在C ++语言的情况下,编译器不会有足够的自由可以互换地使用这些方法 - 在一般情况下它们在C ++中并不是真的可以互换。虽然如果有必要,可以通过模板元编程实现适当的基于大小的[半]自动传递方法选择;但这是一个不同的故事)。

The much more meaningful criterion for selecting the passing method when you write the code "by hand" might sound as follows: 在“手动”编写代码时选择传递方法的更有意义的标准可能听起来如下:

  1. Prefer to pass "by value" when you are passing an atomic, unitary, indivisible entity, such as a single non-aggregate value of any type - a number, a pointer, an iterator. 当您传递一个原子的,单一的,不可分割的实体时,更喜欢传递“by value”,例如任何类型的单个非聚合值 - 数字,指针,迭代器。 Note that, for example, iterators are unitary values at the logical level. 请注意,例如,迭代器是逻辑级别的单一值。 So, prefer to pass iterators by value , regardless of whether their actual size is greater than sizeof(void*). 因此,不管它们的实际大小是否大于sizeof(void *),都希望按值传递迭代器。 (STL implementation does exactly that, BTW). (STL实现就是这样,BTW)。

  2. Prefer to pass "by const reference" when you are passing an aggregate, compound value of any kind. 当您传递任何类型的聚合值时,更喜欢传递“by const reference”。 ie a value that has exposed pronouncedly "compound" nature at the logical level, even if its size is no greater than sizeof(void*). 即在逻辑级别上暴露出明显“复合”性质的值,即使其大小不大于sizeof(void *)。

The separation between the two is not always clear, but that how things always are with all such recommendations. 两者之间的分离并不总是很清楚,但是所有这些建议总是如此。 Moreover, the separation into "atomic" and "compound" entities might depend on the specifics of your design, so the decision might actually differ from one design to the other. 此外,分离成“原子”和“复合”实体可能取决于您的设计的具体情况,因此决策实际上可能因设计而异。

Note, that this rule might produce decisions different from those of the allegedly "correct" size-based method mentioned in this discussion. 请注意,此规则可能会产生与本讨论中提到的所谓“正确”基于大小的方法不同的决策。

As an example, it is interesing to observe, that the size-based method will suggest you manually hard-code different passing methods for different kinds of iterators, depending on their physical size. 作为一个例子,它倾向于观察,基于大小的方法将建议您手动硬编码不同类型的迭代器的不同传递方法,具体取决于它们的物理大小。 This makes is especially obvious how bogus the size-based method is. 这使得基于大小的方法是多么虚伪是特别明显的。

Once again, one of the basic principles from which good programming practices derive, is to avoid basing your decisions on physical characteristics of the platform (as much as possible). 再一次,良好的编程实践所依据的基本原则之一是避免将您的决策建立在平台的物理特性上(尽可能多)。 Instead, you decisions have to be based on the logical and conceptual properties of the entities in your program (as much as possible). 相反,您的决策必须基于程序中实体的逻辑和概念属性(尽可能多)。 The issue of passing "by value" or "by reference" is no exception here. 传递“按价值”或“按参考”的问题在这里也不例外。


In C++11 introduction of move semantics into the language produced a notable shift in the relative priorities of different parameter-passing methods. 在C ++ 11中,将移动语义引入语言产生了不同参数传递方法的相对优先级的显着转变。 Under certain circumstances it might become perfectly feasible to pass even complex objects by value 在某些情况下,按值传递复杂对象可能变得完全可行

Should all/most setter functions in C++11 be written as function templates accepting universal references? 是否应将C ++ 11中的所有/大多数setter函数编写为接受通用引用的函数模板?

Contrary to popular and long-held beliefs, passing by const reference isn't necessarily faster even when you're passing a large object. 与流行的和长期存在的信念相反,即使你传递一个大型物体,传递const引用也不一定更快。 You might want to read Dave Abrahams recent article on this very subject. 您可能想阅读Dave Abrahams最近关于这个主题的文章

Edit: (mostly in response to Jeff Hardy's comments): It's true that passing by const reference is probably the "safest" alternative under the largest number of circumstances -- but that doesn't mean it's always the best thing to do. 编辑:(主要是回应Jeff Hardy的评论):在最大数量的情况下,通过const引用传递可能是“最安全”的选择 - 但这并不意味着它总是最好的事情。 But, to understand what's being discussed here, you really do need to read Dave's entire article quite carefully, as it is fairly technical, and the reasoning behind its conclusions is not always intuitively obvious (and you need to understand the reasoning to make intelligent choices). 但是,为了理解这里讨论的是什么,你真的需要仔细阅读Dave的整篇文章,因为它是相当技术性的,其结论背后的推理并不总是直观明显(你需要理解做出明智选择的理由) )。

Usually for built-in types you can just pass by value. 通常对于内置类型,您只需按值传递即可。 They're small types. 他们是小型的。

For user defined types (or templates, when you don't what is going to be passed) prefer const&. 对于用户定义的类型(或模板,当你没有要传递的东西时)更喜欢const&。 The size of a reference is probably smaller than the size of the type. 引用的大小可能小于类型的大小。 And it won't incurr an extra copy (no call to a copy constructor). 并且它不会产生额外的副本(不调用复制构造函数)。

Well, yes ... the other answers about efficiency are true. 嗯,是的......关于效率的其他答案都是正确的。 But there's something else going on here which is important - passing a class by value creates a copy and, therefore, invokes the copy constructor. 但是这里还有其他一些重要的事情 - 按值传递一个类会创建一个副本,因此会调用复制构造函数。 If you're doing fancy stuff there, it's another reason to use references. 如果你在那里做过花哨的东西,那么使用引用是另一个原因。

A reference to const T is not worth the typing effort in case of scalar types like int, double, etc. The rule of thumb is that class-types should be accepted via ref-to-const. 对于标量类型(如int,double等),对const T的引用不值得输入。经验法则是应该通过ref-to-const接受类类型。 But for iterators (which could be class-types) we often make an exception. 但对于迭代器(可能是类类型),我们经常会例外。

In generic code you should probably write "T const&" most of the time to be on the safe side. 在通用代码中,您应该在大多数情况下写“T const&”以保证安全。 There's also boost's call traits you can use to select the most promising parameter passing type. 还可以使用boost的调用特性来选择最有前途的参数传递类型。 It basically uses ref-to-const for class types and pass-by-value for scalar types as far as I can tell. 据我所知,它基本上使用ref-to-const作为类类型,并使用pass-by-value作为标量类型。

But there are also situations where you might want to accept parameters by value, regardless of how expensive creating a copy can be. 但是,在某些情况下,您可能希望按值接受参数,而不管创建副本的成本是多少。 See Dave's article "Want Speed? Use pass by value!" 请参阅Dave的文章“想要速度?使用价值传递!” .

For simple types like int, double and char*, it makes sense to pass it by value. 对于像int,double和char *这样的简单类型,按值传递它是有意义的。 For more complex types, I use const T& unless there is a specific reason not to. 对于更复杂的类型,我使用const T&除非有特殊原因不这样做。

The cost of passing a 4 - 8 byte parameter is as low as you can get. 传递4 - 8字节参数的成本与您可以获得的一样低。 You don't buy anything by passing a reference. 您不通过传递参考购买任何东西。 For larger types, passing them by value can be expensive. 对于较大的类型,按值传递它们可能很昂贵。

It won't make any difference for an int, as when you use a reference the memory address still has to be passed, and the memory address (void*) is usually about the size of an integer. 对于int来说它没有任何区别,因为当你使用引用时,仍然必须传递内存地址,并且内存地址(void *)通常大约是整数的大小。

For types that contain a lot of data it becomes far more efficient as it avoids the huge overhead from having to copy the data. 对于包含大量数据的类型,它变得更加高效,因为它避免了必须复制数据的巨大开销。

Well the difference between the two doesn't really mean much for ints. 那么两者之间的差异对于整数来说并不是很重要。

However, when using larger structures (or objects), the first method you used, pass by const reference, gives you access to the structure without need to copy it. 但是,当使用较大的结构(或对象)时,您使用的第一个方法(通过const引用)可以访问该结构而无需复制它。 The second case pass by value will instantiate a new structure that will have the same value as the argument. 第二种情况传递值将实例化一个与参数具有相同值的新结构。

In both cases you see this in the caller 在这两种情况下,您都会在调用者中看到此信息

myFunct(item);

To the caller, item will not be changed by myFunct, but the pass by reference will not incur the cost of creating a copy. 对于调用者,myFunct不会更改项目,但是按引用传递不会产生创建副本的成本。

There is a very good answer to a similar question over at Pass by Reference / Value in C++ 在C ++中通过引用/值的类似问题有一个非常好的答案

The difference between them is that one passes an int (which gets copied), and one uses the existing int. 它们之间的区别在于,传递一个int(被复制),一个使用现有的int。 Since it's a const reference, it doesn't get changed, so it works much the same. 因为它是一个const引用,所以它不会被改变,所以它的工作方式大致相同。 The big difference here is that the function can alter the value of the int locally, but not the const reference. 这里最大的区别是函数可以在本地改变int的值,但不能改变const引用。 (I suppose some idiot could do the same thing with const_cast<> , or at least try to.) For larger objects, I can think of two differences. (我想有些白痴可以用const_cast<>做同样的事情,或者至少尝试一下。)对于较大的对象,我可以想到两个不同之处。

First, some objects simply can't get copied, auto_ptr<> s and objects containing them being the obvious example. 首先,一些对象根本无法复制, auto_ptr<>和包含它们的对象就是一个明显的例子。

Second, for large and complicated objects it's faster to pass by const reference than to copy. 其次,对于大而复杂的对象,通过const引用传递比复制更快。 It's usually not a big deal, but passing objects by const reference is a useful habit to get into. 它通常不是什么大问题,但通过const引用传递对象是一个有用的习惯。

Either works fine. 要么工作正常。 Don't waste your time worrying about this stuff. 不要浪费你的时间担心这些东西。

The only time it might make a difference is when the type is a large struct, which might be expensive to pass on the stack. 唯一可能产生影响的是当类型是一个大型结构时,传递堆栈可能会很昂贵。 In that case, passing the arg as a pointer or a reference is (slightly) more efficient. 在这种情况下,将arg作为指针或引用传递(稍微)更有效。

The problem appears when you are passing objects. 传递对象时会出现问题。 If you pass by value, the copy constructor will be called. 如果传递值,则将调用复制构造函数。 If you haven't implemented one, then a shallow copy of that object will be passed to the function. 如果尚未实现,则将该对象的浅表副本传递给该函数。

Why is this a problem? 为什么这是个问题? If you have pointers to dynamically allocated memory, this could be freed when the destructor of the copy is called (when the object leaves the function's scope). 如果您有指向动态分配内存的指针,则可以在调用副本的析构函数时释放(当对象离开函数的作用域时)。 Then, when you re call your destructor, youll have a double free. 然后,当你重新打电话给你的析构函数时,你将获得双倍免费。

Moral: Write your copy constructors. 道德:写你的副本构造函数。

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

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