在c ++中理解引用和副本的问题

时间:2011-04-13 17:29:51

标签: c++ visual-c++

首先,如果我在问题标题中没有使用正确的术语,我会道歉。这完全有可能。我开始学习c ++,我承认我在参考和副本方面遇到了很多困难。参考下面的代码,我有以下问题:

  1. 在main.c中,是传递给Carrier.add()函数(* it)的参数,是我放在向量中的计划对象的引用还是副本,还是其他什么?

  2. 当这个参数被添加到Carrier的计划向量(在运营商类内)时,是否添加了一个副本?这是如何工作的,因为我没有提供复制构造函数或告诉它复制?我认为它是自动生成的?

  3. 如果过于笼统,请忽略此项。我想我的主要问题是我想/需要了解它是如何工作的,这就是我在试图找到我自己的问题的答案时遇到的问题。有没有办法让visual studio在我的实际代码中以与编译器相同的方式生成构造函数,因此我可以在调试模式中逐步执行它们以查看它是如何工作的。现在,当我调试时,我很难告诉编译器生成的复制构造函数被调用(如果这是正在发生的事情)。

  4. 这是我的代码:

    的main.c

    #include "stdafx.h"
    #include <iostream>
    #include "Plan.h"
    #include "Carrier.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
        std::vector<Plan> plans;
    
        for (int i = 0; i < 4; ++i) {
            Plan p(i);
            plans.push_back(p);
        }
    
        Carrier c(5);
        for(std::vector<Plan>::iterator it = plans.begin(); it != plans.end(); ++it) {
                //my question is about the line directly below this comment
            c.add(*it);
        }
        return 0;
    }
    

    Plan.h

    #pragma once
    
    class Plan
    {
    private:
        int id;
    public:
        Plan(int id);
    };
    

    Plan.cpp

    #include "Plan.h"
    
    Plan::Plan(int i)
    {
        id = i;
    }
    

    Carrier.h

    #pragma once
    #include <vector>
    class Plan;
    class Carrier
    {
    private:
        int id;
        std::vector<Plan> plans;
    public:
        Carrier(int i);
        void add(Plan p);
    };
    

    Carrier.cpp

    #include "Carrier.h"
    #include "Plan.h"
    
    Carrier::Carrier(int i) {
        id = i;
    }
    
    void Carrier::add(Plan p) {
        plans.push_back(p);
    }
    

5 个答案:

答案 0 :(得分:3)

(1)由于Carrier::add没有通过引用获取其参数,因此传递了一个副本。要让它参考,请将其签名更改为

void Carrier::add(Plan const &p);

(2)执行push_back时,无论您是通过引用传递Plan还是通过值/副本传递,都会创建副本;这是std::vector(以及所有其他标准容器)语义的一部分。复制构造函数由编译器自动生成。

(3)我不是VC ++用户,但编译器将生成的复制构造函数等同于

Plan::Plan(Plan const &other)
 : id(other.id)  // there's only one member,
                 // but other members would be treated the same
{}

在空体中,您可以添加一个打印语句,指示复制构造函数被调用,但这不会是防水的;在某些情况下,允许C ++执行 copy elision 优化,这意味着跳过了实际的复制。

答案 1 :(得分:2)

在回答第一个问题时,Plan对象按值传递,因此它是传递给函数的对象的副本

如果你想要一个引用,你需要创建这样的函数:

void Carrier::add(Plan const & p)

你在这里传递一个const对象计划,因为你没有在函数中改变任何东西。这在C ++中被认为是一种很好的做法,如果您不打算对参数进行更改并通过引用或指针传递它,请将其设为const

在回答第二个问题时,在这种情况下,容器(std :: vector)会被传递一个副本。

答案 2 :(得分:2)

  

在main.c中,是传递给Carrier.add()函数(* it)的参数,是我放在向量中的计划对象的引用还是副本,还是别的什么?

这是一份副本,因为您已将add()声明为void add(Plan p);。如果要传递引用,则应将其声明为void add(Plan& p);

  

当这个参数被添加到Carrier的计划向量(在运营商类内)时,是否添加了一个副本?这是如何工作的,因为我没有提供复制构造函数或告诉它复制?我认为它是自动生成的?

是。编译器为您生成它。有关详细信息,请参阅here

  

有没有办法让visual studio在我的实际代码中以与编译器相同的方式生成构造函数,因此我可以在调试模式中逐步完成它们以查看它是如何工作的。

据我所知,您无法将其指定为Visual Studio中的设置,以包含自动生成的复制构造函数的代码。

答案 3 :(得分:2)

1- *it取消引用plans向量的元素,并按值将它们传递给c.add。所以,没错,plans向量的每个元素的副本都是在变量padd方法的参数)中临时构造的,然后添加到plans向量的Carrier向量中。 {{1}}上课。

2-那是对的,增加了副本。不提供复制构造函数时的默认行为是复制要复制的对象的每个属性。您可以通过提供复制构造函数来修改此行为。

3-理解编译器生成的复制构造函数是如何工作的并不难。假设传递的对象的每个属性都被复制到其拷贝构造函数被调用的对象。

答案 4 :(得分:2)

为了理解pass-by-valuepass-by-reference,您需要知道什么是引用。 Referencesaliases的内存位置相同。

虽然下面的代码与您的问题无关,但我故意用一个简单的例子来说明发生了什么。

#include<iostream>
void foo(int , int & ); //Declaration of foo

void foo(int copy, int &reference)
{
          copy++; // Updates the Local Variable Copy 
          reference++;  // Updates x
}

int main(){
    int x=10;
    int &reftox=x; // reference to x
    foo(x,reftox);
    std::cout << x;
}

输出为11。与你的问题有关的其余疑问已经得到解答。