C++:基于浅拷贝/深拷贝对模拟string类的一些优化

简介: C++:基于浅拷贝/深拷贝对模拟string类的一些优化

string类和日期类

前面我们已经实现了string类和日期类,这两个类有没有想过它们有什么不同?

其实答案很明显,不同的地方在于string类中涉及到内存空间开辟,而日期类只是简单的对年月日三个变量进行一些变换

那有没有内存空间开辟对实际实现代码有什么影响?看下面代码

// error
class my_string
{
public:
  my_string()
  {
    _size = 0;
    _capacity = 0;
    _str = new char[_capacity+1];
  }
  ~my_string()
  {
    delete[] _str;
  }
private:
  size_t _size;
  size_t _capacity;
  char* _str;
};
int main()
{
  my_string s1;
  my_string s2;
  s2 = s1;
  return 0;
}

上面代码是可以运行吗?答案是不可以,原因在于什么?

编译器默认生成了六个成员函数,这当中包含了赋值重载,而代码中的s2=s1在编译器看来,就是把s1对象中的三个成员赋值给s2,这个过程就是所谓的浅拷贝,s2中的数据成员和s1完全相同,因此出了函数作用域后,s2会被先析构,s1继续析构,而s2被析构后它们所指向的空间已经被释放了,s1还要继续释放,很明显这是不可以的,因此程序会报错

因此我们就得出了一个初步结论,当成员变量涉及到动态开辟内存空间的时候,如果使用的还是编译器默认的赋值重载函数,就会涉及到一个空间被析构两次导致错误,这就是浅拷贝的弊端

浅拷贝/深拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规

那如何解决?就需要用到深拷贝的知识:

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享

对于上述代码的深拷贝写法

class my_string
{
public:
  my_string()
  {
    _size = 0;
    _capacity = 0;
    _str = new char[_capacity+1];
  }
  ~my_string()
  {
    delete[] _str;
  }
  my_string& operator=(const my_string& s)
  {
    _size = s._size;
    _capacity = s._capacity;
    _str = new char[_capacity + 1];
    strcpy(_str, s._str);
    return *this;
  }
private:
  size_t _size;
  size_t _capacity;
  char* _str;
};
int main()
{
  my_string s1;
  my_string s2;
  s2 = s1;
  return 0;
}

那我们采用深拷贝的写法,就解决了这个问题,在前文string模拟实现中采用的也确实是这个方法,那这个方法可不可以进行继续优化?这就是后文要讨论的知识:如何优化深拷贝

正常版本和优化版本

对于深拷贝,上述的写法是一个正常的版本,而事实上这个版本是可以被优化的,例如可以这样优化:

my_string(const my_string& s)
    :_str(nullptr)
    ,_size(0)
    ,_capacity(0)
  {
    my_string tmp(s._str);
    swap(tmp);
  }

上面就是一种对深拷贝的优化,我们可以借助一个tmp变量,构造出一个对象,再把这个对象和*this进行交换,这样也能实现深拷贝,这就是优化版本

写时拷贝

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源

简单来说,写时拷贝就是在需要的时候再进行深拷贝,如果不进行写入就起到了提升效率的作用

相关文章
|
17天前
|
存储 安全 C语言
C++ String揭秘:写高效代码的关键
在C++编程中,字符串操作是不可避免的一部分。从简单的字符串拼接到复杂的文本处理,C++的string类为开发者提供了一种更高效、灵活且安全的方式来管理和操作字符串。本文将从基础操作入手,逐步揭开C++ string类的奥秘,帮助你深入理解其内部机制,并学会如何在实际开发中充分发挥其性能和优势。
|
17天前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
1天前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、"+"操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
|
20天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1天前
|
存储 JavaScript Java
课时44:String类对象两种实例化方式比较
本次课程的主要讨论了两种处理模式在Java程序中的应用,直接赋值和构造方法实例化。此外,还讨论了字符串池的概念,指出在Java程序的底层,DOM提供了专门的字符串池,用于存储和查找字符串。 1.直接赋值的对象化模式 2.字符串池的概念 3.构造方法实例化
|
20天前
|
C++
模拟实现c++中的string
模拟实现c++中的string
|
17天前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
17天前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
2月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
74 19
|
2月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
64 13