具有非原始类型的 C++ 值传递?

Posted

技术标签:

【中文标题】具有非原始类型的 C++ 值传递?【英文标题】:C++ pass-by-value with non-primitive types? 【发布时间】:2013-08-23 05:11:04 【问题描述】:

我一定对 C++11 有一个根本性的误解。我的教授告诉我,除了通过引用或指针之外,不可能将非原始类型传递给函数。但是,以下代码可以正常工作

#include <iostream>
using namespace std;

class MyClass

public: 
    int field1;
;

void print_string(string s)  
    cout << s << endl; 


void print_myclass(MyClass c)  
    cout << c.field1 << endl; 


int main(int argc, char *argv[]) 

    string mystr("this is my string"); 
    print_string(mystr); // works
    MyClass m; 
    m.field1=9; 
    print_myclass(m); 
    return 0;

运行程序产生以下输出

this is my string
9

RUN SUCCESSFUL (total time: 67ms)

我在 Win7 上使用 MinGW/g++

为什么会这样?我以为非原始类型不能按值传递?!

【问题讨论】:

你的教授错了,如果他是这么说的话。更有可能的是,他说这是不可取的。 @EJP 如果他说这是不可取的,作为一个笼统的陈述,那么他也是错的 ;-) 虽然这是“过时的或没有经过深思熟虑的性能建议”的错误,而不是“不懂语言基础”有点错误,这稍微好一点。 好的。谢谢!我想这是不可取的,因为它会导致每个参数对象的另一个副本? @JaredLindsey,是的,它会调用类上的复制构造函数,除非它已被显式删除或设为私有。然后您的代码无法编译。如果复制构造函数已被覆盖,则传递的参数必须符合您明确定义的复制构造函数的定义。 编程教授不一定是优秀的程序员。被警告! 【参考方案1】:

非原始类型当然可以按值传递。 (这在 C++ 标准的第 5.2.2 节[expr.call] 中有介绍。)

但是,有几个原因经常不鼓励这样做,尤其是在 C++03 代码中。

首先,对于大对象,这样做的效率较低(与通过引用传递相比),因为数据是在堆栈上传递的。引用将在堆栈上占用一个单词,因此通过堆栈传递大于一个单词的任何对象必然会更慢。

其次,按值传递调用复制构造函数(或者,正如@templatetypedef 指出的那样,可能是 C++11 中的移动构造函数)。这种额外的处理可能会产生一定的开销。

第三,您可能打算修改传入的对象,但是通过传入一个副本(按值),您在函数中所做的任何更改都不会影响原始对象。因此,确保语义正确(即是否要修改原件)非常重要。因此,在某些情况下这是一个潜在的错误。

最后,如果有一个写得不好的类没有复制构造函数或赋值运算符,编译器会自动为你生成一个默认的。这将执行浅拷贝,这可能会导致内存泄漏等问题。这是实现这些特殊方法非常重要的另一个很好的理由。完整的详细信息在这篇文章中:

The Rule of Three in C++

一般来说,对于 C++03 代码,如果您不打算修改对象,通常会通过 const&amp; 引用,如果您需要修改对象,则通常会通过正常的 &amp; 引用。如果参数是可选的,则使用指针。

在这些问题中也找到了一些很好的答案和讨论,尤其是关于移动语义的讨论:

Pass by Reference / Value in C++ C++: Reasons for passing objects by value What are move semantics? What's the difference between passing by reference vs. passing by value?

C++11 的完整答案更复杂:

Is pass-by-value a reasonable default in C++11?

可能是使用哪种方法的最佳总结:

How to pass objects to functions in C++?

【讨论】:

“通常效率很低”?撇开大量难以复制的类以及只有一个或几个单词大的类,还有这个:cpp-next.com/archive/2009/08/want-speed-pass-by-value 如果由于默认值错误而导致复制导致泄漏/错误,那么这是类的错误,应该通过禁止复制来修复(如果你不这样做,即使是 C++11 之前的版本也很容易)不要介意无用的错误信息)或遵守三/五/零的规则。 因此是“通常”限定词。很难用 25 个字或更少的字来表达所有这些微妙之处。当然,完整的答案更加细致入微。但是对于教新手来说,这是一个很好的经验法则。 RE 性能:不要向新手提供关于他们还不需要知道的问题的不准确信息,并且无论如何应该在以后全面了解问题? ;-) 除了这种哲学上的反对之外,“通常”和“绝大多数”都是不正确的:有很多很多的对象永远不正确,而对于几乎所有其他对象,它仅在有限的情况下是正确的,因此如果您确定包含有关该主题的任何评论,则“有时”或“在特定情况下”会更合适。 确实 - 更新了答案以澄清泄漏是由于编写不当的类和需要复制 ctor 和分配操作。【参考方案2】:

您的教授完全错了,也许他在考虑 JAVA 或 C#?在 C++ 中,一切都是按值传递的。要通过引用传递某些内容,您需要使用 & 修饰符传递它。

【讨论】:

【参考方案3】:

非原始类型确实可以在 C++ 中按值传递。如果您尝试这样做,C++ 将使用称为 复制构造函数(或在 C++11 中的某些情况下,移动构造函数)的特殊函数来初始化参数作为论据的副本。众所周知,编写复制构造函数和赋值运算符是 C++ 的一个棘手部分(出错容易,而正确则很难),因此教授可能试图阻止您这样做。未能编写复制构造函数或编写不正确很容易导致程序崩溃,并且是新 C++ 程序员常见的困惑来源。

我建议在 Google 上搜索“C++ Rule of 3”或“复制构造函数赋值运算符”,以了解有关如何编写智能复制对象的函数的更多信息。了解如何做到这一点需要一点时间,但是一旦你理解了这些概念,这并不难。

希望这会有所帮助!

【讨论】:

以上是关于具有非原始类型的 C++ 值传递?的主要内容,如果未能解决你的问题,请参考以下文章

在Java中将原始类型作为输出参数传递

我是不是需要在 C# 中使用来自非托管 C++ dll 的原始数据类型执行 MarshalAs?

C ++中的原始类型与C中的原始类型[重复]

在android中的活动之间传递非原始类型的数据

在 C# 中使用非原始类型重新创建 C++ 联合类型时出现对齐错误

可以将任何原始类型传递给需要指针的函数吗?