使用指针的C++拷贝构造函数

Rak*_*noy 9 c++ pointers copy-constructor

任何人都可以解释*p=*q这个C++代码的含义吗?这是一个复制构造函数的概念吗?

class A{
  //any code
}

int main(){
  A *p=new A();
  A *q=new A();
  *p=*q;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

Arn*_*rah 13

这是一个复制构造函数的概念吗?

不,你指的是复制任务概念.考虑一下:

int* p = new int{9};
int* q = new int{10};
*p = *q;
Run Code Online (Sandbox Code Playgroud)

如您所见,仅q复制指向的变量的值.这相当于对象的复制分配.如果你这样做:

p = q;
Run Code Online (Sandbox Code Playgroud)

然后这不是复制赋值,因为它们都int指向相同的地址和值,这意味着对其他变量的任何更改pq将反映在另一个变量上.为了给出一个更具体和验证的例子,这里有一些代码:

int main() 
{
    int* p = new int{9};
    int* q = new int{10};
    *p = *q;
    //p = 10, q = 10
    *p = 11;
    //p = 11, q = 10
    delete p;
    delete q;
}
Run Code Online (Sandbox Code Playgroud)

这是一个补充反例

int main() 
{
    int* p = new int{9};
    int* q = new int{10};
    p = q;
    //p = 10, q = 10
    *p = 11;
    //p = 11, q = 11
    delete p;
    //delete q; Not needed because p and q point to same int
}
Run Code Online (Sandbox Code Playgroud)

如您所见,这些变化反映在两个变量上 p=q

附注 你提到了复制构造,但你不清楚这个概念.这是复制结构的样子:

int* p = new int{9};
int* q = new int{*p}; //q=9
Run Code Online (Sandbox Code Playgroud)

复制构造与复制分配的不同之处在于,对于复制构造,变量尚未具有值,对于对象,构造函数尚未被调用.混合使用这两个术语是很常见的,但根本的区别在于这两个概念是完全不同的.


Muh*_*mad 9

看起来,您不清楚复制构造函数和复制赋值.让我们先来看看这两个概念,然后我会回答你的问题.答案有点长,所以请耐心等待:)

复制构造函数

在这里,我不打算解释如何编写复制构造函数,但是当调用复制构造函数时,它不会.(如果你想知道,如何编写一个拷贝构造函数,请看这个)

复制构造函数是一种特殊的构造函数,用于将新对象创建为现有对象的副本.(只要需要制作现有对象的副本,就会调用它)

这些是复制构造函数将被调用以生成现有对象的副本的场景:


  • 使用一些先前创建的对象初始化对象:

    SomeClass obj;
    // ...
    SomeClass anotherObj = obj; // here copy constructor will be called.
    
    Run Code Online (Sandbox Code Playgroud)

    看,SomeClass obj;语句只是创建一个对象(这里,默认构造函数将被调用来创建对象).第二个语句SomeClass anotherObj = obj;是实例化一个对象,用obj(现有对象)的值初始化,因此这里将调用复制构造函数.您也可以这样使用现有对象初始化对象:( SomeClass anotherObj(obj);此语句相当于SomeClass anotherObj = obj;)

    除外:
    如果使用某个rvalue表达式初始化.例如

    SomeClass someObject = aObject + anotherObject;
    
    Run Code Online (Sandbox Code Playgroud)

    在这种情况下,将调用移动构造函数.看,什么是移动语义?


  • 按值将对象传递给某个函数(请参阅按值传递参数):

    请参阅以下代码片段,此处函数doSomething通过值接受对象作为参数:

    void doSomething(SomeClass someObject)
    {
        // ...
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在某些情况下,当需要在参数对象中制作传递参数的副本时someObject,我列出了何时需要制作副本以及何时不需要.

    看看下面的代码片段:

    SomeClass someObject;
    // ...
    doSomething(someObject); // here copy constructor will be called.
    
    Run Code Online (Sandbox Code Playgroud)

    该语句SomeClass someObject;只是someObject通过调用默认构造函数来实例化.

    第二个语句通过传递参数doSomething(someObject);来调用doSomething先前显示的函数someObject.当需要制作someObject传递给函数的副本时就是这种情况.

    除了:
    Similiary,如果我们调用doSomething一些rvalue表达式,它将调用move构造函数而不是copy构造函数.


  • 按值从函数返回对象:

    我们来看看下面的定义 doSomething

    SomeClass doSomehing()
    {
        SomeClass someObject;
        // ...
        return someObject;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    在上面的函数中doSomething,SomeClass正在创建一个对象,在完成一些任务之后,该函数返回该对象,在这种情况下,someObject将创建并返回该对象的副本.

    除了:
    Similiary,如果doSomething返回一些rvalue表达式,它将调用move构造函数而不是copy构造函数.


复制分配

复制分配通常与复制构造混淆,让我们看看它与复制构造的不同之处:

SomeClass someObject;
// ...
SomeClass anotherObject;
// ...
anotherObject = someObject; // here copy assignment operator will be called.
Run Code Online (Sandbox Code Playgroud)

前两个语句只是创建someObjectanotherObject,你看,第三条语句实际上是调用拷贝赋值运算符,并没有拷贝构造函数.

只有在创建一些新对象时才会调用构造函数.在这种情况下anotherObject = someObject;,两个对象都已创建,因此不会对复制构造函数进行任何调用.而是调用复制赋值运算符(要查看如何重载复制赋值运算符,请参阅此处)


现在,让我们来看看你的代码片段:

A *p=new A();
A *q=new A();
*p=*q;
Run Code Online (Sandbox Code Playgroud)

在第一条语句A *p=new A();,默认的构造函数将被调用,以创建一个对象(在这种情况下,新对象将在堆上创建)和p将与新创建的对象的地址进行初始化(作为p一个指针)

与第二个语句类似A *q=new A();(它正在创建另一个对象,q并将使用新创建的对象进行初始化)

现在,第三个声明:( *p = *q;这里*Dereference运算符)

要理解第三个语句正在做什么,让我们看看一些指针并取消引用它们以获得它们指向的实际对象.

int someVariable = 5;
int *somePointer = &someVariable;
// ...
*somePointer = 7;
Run Code Online (Sandbox Code Playgroud)

让我们尝试理解上面的代码片段:someVariable使用值创建并初始化5,然后somePointer创建并使用地址初始化someVariable.

现在,最后一个语句*somePointer = 7;,它实际上是取消引用somePointer和通过取消引用,它将获得它指向的变量.(所以它会得到someVariable)然后分配7给它.所以在这个陈述之后,someVariable价值就会变成7

我们有另一个例子:

int* somePointer = new int;
int* anotherPointer = new int;
// ...
*somePointer = 5;
*anotherPointer = 7;
// ...
*somePointer = *anotherPointer;
Run Code Online (Sandbox Code Playgroud)

首先,somePointer将使用动态分配的int变量的地址创建和初始化(将在堆中分配,请参阅c ++中的动态分配),类似地,anotherPointer将使用另一个动态分配的变量的地址进行初始化.

然后,它分配5给第一个变量(正在被指向somePointer)和7第二个变量(正在被指向anotherPointer)

现在,最后一个语句将是您感兴趣的,*somePointer = *anotherPointer;在此语句中*somePointer是取消引用并获取第一个变量(其值为5)并且*anotherPointer正在取消引用并获取第二个变量(其值为7),并且它正在分配第二个变量到第一个变量,导致第一个变量的值改为7.

现在,让我们看看你的*p=*q;语句,(p指向第一个对象A,并q指向A动态分配的第二个对象),*p将取消引用p并获取第一个对象,*q将取消引用q并获取第二个对象,以及然后第二个对象将被复制到第一个对象中,如果你看到,没有创建新对象*p=*q;,只在第一个对象中创建了第二个对象的值,所以这里将调用复制赋值运算符而不是复制构造函数.

另一件事

您应该使用new运算符取消分配您借用的动态分配的内存:

delete p;
delete q;
Run Code Online (Sandbox Code Playgroud)

您应该在程序结束时添加这两行,以避免内存泄漏.


dav*_*igh 5

如评论中所述,首先需要了解基础知识:

  • A *p=new A();一个得到一个指针,指向的存储区域上,其中类型的对象堆A构造.

  • 使用*p一个解引用指针,即一个检索对象.

  • 现在*p = *q使用类的(可能是隐式声明的)赋值运算符,A以便给出*p- 指向的对象p- 的值*q.此操作可以等效地写为p->operator=(*q).

最后一步,即赋值,与使用对象而不是指针获得的内容相同(这通常是使用C++并且通常称为RAII的更好方式):

A r;
A s;
r=s;
Run Code Online (Sandbox Code Playgroud)