【问题标题】:Is this pass by reference or by value?这是按引用传递还是按值传递?
【发布时间】:2012-03-20 10:41:12
【问题描述】:

假设我有以下内容:

class MyClass
{
    // ...
}

void doSomething (MyClass instance)
{
    // Is instance passed by reference or by value (copied)?
}

void main ()
{
    MyClass instance = MyClass();

    doSomething(instance);
}

doSomething() 中的 instance 是通过引用传递的吗?还是该类在内存中重复?还是别的什么?

【问题讨论】:

  • 幕后发生了什么?整个对象是否在内存中重复?
  • 你得看看复制构造函数做了什么。
  • you param instance 在这种情况下由编译器定义的复制构造函数构造。
  • 希望你有一个默认的构造函数,否则 MyClass() 将无法工作。
  • C++ 在上述情况下调用复制构造函数并将其传递。它是一个浅拷贝。如果你想要一个深拷贝,你需要在你的课堂上覆盖它。

标签: c++ pass-by-reference pass-by-value


【解决方案1】:

如果没有明确说明,则不是引用。这是按价值计算的。

以下原型表示通过引用传递的参数:

void doSomething (MyClass& instance)
{
    // Is instance passed by reference or by value (copied)?
    // In this case, by reference
}

确实,在您的情况下,最有可能创建了一个新对象。我说最有可能是因为,只要可观察到的行为是相同的,它也可能不是。但是,理论上,是的,会创建一个新对象供函数内部使用。

新对象是通过调用作为参数传递的对象的复制构造函数来创建的。如果您还没有定义复制构造函数,则编译器会生成一个默认值,它会执行浅拷贝。

【讨论】:

  • “我说最有可能是因为,只要可观察到的行为是相同的,它也可能不是”>> 无法理解,请您解释一下吗?
  • @Mr.Anubis 编译器可以优化掉多余的对象。
  • 有没有办法扭转这种情况。我的意思是,有没有办法对MyClass 进行编码,如果没有明确说明,它是通过引用而不是值传递的?
【解决方案2】:

这是按值传递的

void doSomething (MyClass instance)

这是通过引用传递的

void doSomething (MyClass& instance)

这是通过 const 引用传递的

void doSomething (const MyClass& instance)

另外MyClass 不需要通过赋值构造。 所以:

MyClass mc=MyClass();

实际上等同于:

MyClass mc; //no parens needed for default constructor (no args).

编辑: 这是通过 const 引用传递给 const 函数的,可以在 const 对象上调用 const 函数,因为它保证不会修改对象状态。

void doSomething (const MyClass& instance) const

与许多不太严格的语言不同,C++ 中的常量正确性被认为是一种良好的做法。

见我:

http://en.wikipedia.org/wiki/Const-correctness

http://www.gotw.ca/gotw/006.htm

【讨论】:

  • const关键字除了不能在doSomething()方法中修改之外还有什么意义?
  • @Ben 它允许将临时对象作为参数传递。
  • 当函数不需要修改对象的状态时,应该使用常量来读取它。您也应该将您的函数标记为 const(我会在 min 中添加一个示例),然后可以在对象为 const 时使用这些函数。
  • @Ben 就是这样。尽管效率低下,但按值传递可保护调用者免受函数对对象的修改。使用 const 引用提供了几乎相同的保护。然而,一个糟糕的函数编写者可以抛弃 constness。
  • 所以,我把它记下来,当一个类对象是 const 时,我只能调用它的 const 方法,对吗?
【解决方案3】:

是的,它被复制了。

当您调用doSomething 时,会调用MyClass 复制构造函数,以创建一个新实例。

此实例将在doSomething 函数的持续时间内保持在范围内。当函数结束时,将为此实例调用MyClass 析构函数。

(请注意,如果您还没有编写复制构造函数,则默认为您创建一个。)

所以如果你添加一个显式的拷贝构造函数和析构函数:

class MyClass
{
    public:
         MyClass()
         {
             std::cout << "MyClass constructor" << std::endl;
         }
         MyClass(const MyClass& other)
         {
               std::cout << "MyClass copy constructor" << std::endl;
         }
         MyClass::~MyClass()
         {
               std::cout << "MyClass destructor" << std::endl;
         }
}
void doSomething (MyClass instance)
{
    std::cout << "doSomething method";
}

void main ()
{
    MyClass instance = MyClass();
    std::cout << "invoking doSomething" << std::endl;
    doSomething(instance);
    std::cout << "returned from doSomething" << std::endl;
}

这将输出以下内容:

  • MyClass 构造函数
  • 调用 doSomething
  • MyClass 复制构造函数
  • doSomething 方法
  • MyClass 析构函数
  • 从 doSomething 返回
  • MyClass 析构函数

【讨论】:

    【解决方案4】:

    它在堆栈上被复制以便传递给函数。如果您的类已经达到一定数量的字节,这可能会非常昂贵,因为每个实例变量都必须放在堆栈上。在这种情况下,一个类的行为与常规 c 结构没有什么不同。

    假设你的班级有 2 个整数(我假设是 32 位系统)

    class A {
        int a;
        int b;
    };
    

    当您在 main 中声明它时,堆栈指针下降 8 个字节。 如果你调用 your_function(A),C 还必须将布局类复制到堆栈中,以便 your_function 在被调用后可以访问它。这意味着堆栈指针再次下降 8 个字节,从旧值写入值并调用函数。

    有 2 个实例变量,这没什么大不了的。但是图像你有一个包含结构的类,让我们说 20 个整数 + 一个虚拟表?比我可以是一个非常昂贵的操作。如果你通过指针传递,堆栈指针只需下降 4 个字节,复制其中类的地址并调用函数。当然更便宜。

    您实际上可以自己尝试。只需更改函数中的一些实例变量,然后在函数返回后检查您的第一个类是否具有新值或函数调用之前的值。那应该会给你答案。

    无论如何,如果这是你想要的,你应该实现一个复制构造函数来处理如何复制你的变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-12
      • 2016-11-28
      • 2017-11-14
      相关资源
      最近更新 更多