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

C++ compiler 'shallow' copies and assignments

本文关键字:赋值 副本 shallow C++ 编译器      更新时间:2023-10-16

我正在上c++的面向对象编程课。

在我们的文本中,它说

如果没有声明复制构造函数,编译器将插入代码这实现了一个浅拷贝。如果不声明赋值操作符时,编译器插入实现任务。

我想知道的是,这是不是真的,所提到的编译器机制实际上是什么,以及它是如何工作的。

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

EDIT>更多的上下文

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

复制构造函数的定义包含以下逻辑:
  1. 对所有非资源实例变量执行浅拷贝
  2. 为每个新资源分配内存
  3. 将数据从源资源复制到新创建的资源

由文本

定义的资源

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

这个资源的管理需要额外的逻辑,这对于不访问资源的简单类来说是不必要的。这个附加的逻辑确保正确处理资源,通常称为深度复制和任务。

更准确地说,编译器定义了一个默认的复制构造函数和一个默认的复制赋值操作符。只需调用所有成员变量的复制构造函数,就可以复制/构造新对象。

    对于像int s和float s这样的原语,这通常不是问题。
  • 对于指针来说。这是个坏消息!当第一个对象删除指针时会发生什么?现在另一个对象的指针无效了!
  • 如果成员变量不能复制(也许您使用std::unique_ptr来解决上述问题),那么默认的复制分配/ctor将不起作用。你怎么能复制无法复制的东西呢?这将导致编译错误。

如果你定义了自己的复制构造函数/赋值操作符,你可以做一个"深度复制"。您可以:

  • 创建一个新对象,而不是复制指针
  • 显式地"浅拷贝"指针
  • 根据你真正想要的混合以上两种!
  • 在复制的对象中使用默认/自定义值初始化成员变量,而不是复制原始对象中的任何内容。
  • 完全禁用复制
  • 开,开,开

可以看到,有很多原因可以解释为什么要实现(或显式禁止)自己的复制赋值操作符、复制构造函数、它们的move对应函数和析构函数。事实上,有一个著名的c++成语叫做"五法则"(以前称为"三法则"),它可以指导您决定何时执行此操作。

是的,这确实被称为浅复制。至于它是如何工作的,假设你有一个指针变量,你把它赋值给另一个指针变量。这只是复制指针,而不是它所指向的内容,这是一个浅层复制。复制会创建一个新指针,并复制第一个指针所指向的实际内容。

像这样:

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;,则两个指针都无效。如果两个指针在某个类的不同对象中,那么指针之间没有真正的连接,第二个对象将不知道第一个对象是否删除了它的内存。

浅复制的代码是对每个字段的简单赋值。如果:

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的隐式声明的复制构造函数将具有form X::X(const X&) if

- X的每个直接基类或虚基类B都有复制构造函数,其第一个形参类型为const B&或常量, and

-用于X的所有非静态数据成员对于类类型M(或其数组),每个这样的类类型都有一个副本第一个形参类型为const M&amp的构造函数;或常量挥发性M& .123

否则,隐式声明的复制构造函数格式为X::X(X&)

12.8 -28说:

用于非联合的隐式定义的复制/移动赋值操作符类X对其子对象执行按成员复制/移动赋值。[…的顺序,

我将使用一个基本类来定义编译器的行为,这是我所知道的最好的。

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++源代码或代码不是一个问题。这两者在预编译阶段的定义方式的不同决定了编译器在开始将源代码编译成目标代码之前将如何工作。

请记住,标准库字符串&容器确实实现了自己的复制构造函数&赋值操作符!但是,如果类具有int、float、double等基本类型,那么同样的概念也适用于编译器的行为。因此,编译器将根据简单类的声明以同样的方式对待它。

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
};

编译器将以同样的方式处理这个类;它不会定义这两个,因为它们将不会被实现,因为它们被声明为私有的。