C ++编译器'浅'副本和赋值

时间:2015-10-22 16:58:30

标签: c++ compiler-construction shallow-copy

我正在使用C ++学习面向对象编程。

在我们的文中它说,

  

如果我们不声明复制构造函数,编译器会插入代码   实现浅拷贝。如果我们不宣布任务   运算符,编译器插入实现浅的代码   分配

我想知道的是,这实际上是否真实,实际调用了编译器机制的内容,以及它是如何工作的。

这是关于复制构造函数的问题,它与编译器行为有关。

编辑>更多背景

复制构造函数,如文本所定义:

  

复制构造函数的定义包含

的逻辑      
      
  1. 对所有非资源实例变量
  2. 执行浅表复制   
  3. 为每个新资源分配内存
  4.   
  5. 将数据从源资源复制到新创建的资源
  6.   

资源由文本

定义
  

对象在运行时分配的内存表示该资源   对象的类。

     

此资源的管理需要额外的逻辑,这对于不访问资源的更简单的类是不必要的。这个额外的逻辑   确保正确处理资源,通常称为深度复制和   分配。

4 个答案:

答案 0 :(得分:7)

是的,这是真的,它的确称为浅拷贝。至于它是如何工作的,假设你有一个指针变量,并将它分配给另一个指针变量。这只复制指针而不是它指向的内容,这是一个浅拷贝。 deep 副本会创建一个新指针,并复制第一个指针指向的实际内容。

这样的事情:

int* a = new int[10];

// Shallow copying
int* b = a;   // Only copies the pointer a, not what it points to

// Deep copying
int* c = new int[10];
std::copy(a, a + 10, c);  // Copies the contents pointed to by a

关于指针的浅复制问题应该非常明显:在上面的示例中b初始化之后,你有两个指针指向同一个内存。如果然后delete[] a;,则两个指针都变为无效。如果两个指针位于某个类的不同对象中,则指针之间没有真正的连接,第二个对象将不知道第一个对象是否已删除其内存。

答案 1 :(得分:7)

更准确地说,编译器定义了默认复制构造函数和默认复制赋值运算符。这些将通过简单地为所有成员变量调用复制构造函数来复制/构造新对象。

  • 对于intfloat这样的原语,这通常不是问题。
  • 尽管如此。这是坏消息!第一个对象删除指针时会发生什么?现在另一个对象的指针无效!
  • 如果无法复制成员变量(可能您使用std::unique_ptr来解决上述问题),则默认的复制赋值/ ctor将无效。你怎么能复制一些无法复制的东西?这将导致编译器错误。

如果您定义自己的复制构造函数/赋值运算符,则可以改为使用“深层复制”。你可以:

  • 创建一个新对象,而不是复制指针
  • 明确地“浅拷贝”指针
  • 根据您的实际需要混合上述两个!
  • 使用复制对象中的默认值/自定义值初始化成员变量,而不是复制原始对象中的任何内容。
  • 完全禁用复制
  • 一直打开

正如您所看到的,您有很多理由想要实现(或明确禁止)您自己的复制赋值运算符,复制构造函数,移动对应项和析构函数。事实上,有一个众所周知的C ++成语The Rule of Five(以前称为3规则)可以指导您决定何时执行此操作。

答案 2 :(得分:2)

浅拷贝的代码是每个字段的简单分配。如果:

class S {
  T f;
};
S s1, s2;

s1=s2;这样的作业等同于以下内容:

class S {
    T f;
  public:
    S &operator=(const S&s) {
      this->f = s.f; // and such for every field, whatever T is
    }
};
S s1, s2;
s1=s2;

标准草案的12.8-8中说明了这一点:

  

类X的隐式声明的复制构造函数将具有   表格X :: X(const X&)if

     

- X的每个直接或虚拟基类B.   复制构造函数,其第一个参数是const B&的类型。或const   挥发性B&和

     

- 对于X的所有非静态数据成员   对于类型M(或其数组),每个这样的类类型具有副本   构造函数,其第一个参数是const M&的类型。或const   挥发性M& .123

     

否则,隐式声明的复制构造函数   将具有X :: X(X&)

的形式

12.8-28说:<​​/ p>

  

非联合的隐式定义的复制/移动赋值运算符   class X执行其子对象的成员复制/移动分配。 [...]按照在课程定义中声明它们的顺序。

答案 3 :(得分:0)

我将使用基本类来定义编译器的行为,因为我知道如何操作。

class Student sealed {
private:
    std::string m_strFirstName;
    std::string m_strLastName;

    std::vector<unsigned short> m_vClassNumbers;
    std::vector<std::string> m_vTeachers;

    std::vector<unsigned short> m_vClassGrades;

public:
    Student( const std::string& strFirstName, const std::string& strLastName );

    std::string getFirstName() const;
    std::string getLastName() const;

    void setClassRoster( std::vector<unsigned short>& vClassNumbers );
    std::vector<unsigned short>& getClassRoster() const;

    void setClassTeachers( std::vector<std::string>& vTeachers );
    std::vector<std::string>& getClassTeachers() const;

    void setClassGrades( std::vector<unsigned short>& vGrades );
    std::vector<unsigned short>& getGrades() const;

    // Notice That These Are Both Commented Out So The Compiler Will
    // Define These By Default. And These Will Make Shallow / Stack Copy
    // Student( const Student& c ); // Default Defined 
    // Student& operator=( const Student& c ); // Default Defined
};

此类的版本及其默认声明将构造一个复制构造函数和一个相等的运算符。

class Student sealed {
private:
    std::string m_strFirstName;
    std::string m_strLastName;

    std::vector<unsigned short> m_vClassNumbers;
    std::vector<std::string> m_vTeachers;

    std::vector<unsigned short> m_vClassGrades;

public:
    Student( const std::string& strFirstName, const std::string& strLastName );

    std::string getFirstName() const;
    std::string getLastName() const;

    void setClassRoster( std::vector<unsigned short>& vClassNumbers );
    std::vector<unsigned short>& getClassRoster() const;

    void setClassTeachers( std::vector<std::string>& vTeachers );
    std::vector<std::string>& getClassTeachers() const;

    void setClassGrades( std::vector<unsigned short>& vGrades );
    std::vector<unsigned short>& getGrades() const;     

private:
    // These Are Not Commented Out But Are Defined In The Private Section
    // These Are Not Accessible So The Compiler Will No Define Them
    Student( const Student& c ); // Not Implemented
    Student& operator=( const Student& c ); // Not Implemented
};

这个类的第二个版本不会,因为我声明它们都是私有的!

这可能是我证明这一点的最佳方式。我只展示了这个类的头文件接口,因为要编译的c ++源代码或代码不是一个问题。在预编译阶段定义这两者的方式的不同决定了编译器在开始将源代码编译成目标代码之前将如何工作。

请记住标准库字符串&amp;容器确实实现了自己的Copy Constructor&amp;作业运营商!但是,如果类具有基本类型(如int,float,double等),则相同的概念适用于编译器的行为。因此,编译器将根据其声明以相同的方式处理Simple类。

class Foo {
private:
    int   m_idx;
    float m_fValue;

public:
    explicit Foo( float fValue );

    // Foo( const Foo& c ); // Default Copy Constructor
    // Foo& operator=( const Foo& c ); // Default Assignment Operator
};

第二版

class Foo {
private:
    int   m_idx;
    float m_fValue;

public:
    explicit Foo( float fValue );

private:
    Foo( const Foo& c ); // Not Implemented
    Foo& operator=( const Foo& c ); // Not Implemented
};

编译器将以相同的方式处理此类;它不会定义其中任何一个,因为它们不会被声明为私有。