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,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源

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

相关文章
|
3天前
|
设计模式 安全 编译器
【C++11】特殊类设计
【C++11】特殊类设计
22 10
|
8天前
|
C++
C++友元函数和友元类的使用
C++中的友元(friend)是一种机制,允许类或函数访问其他类的私有成员,以实现数据共享或特殊功能。友元分为两类:类友元和函数友元。类友元允许一个类访问另一个类的私有数据,而函数友元是非成员函数,可以直接访问类的私有成员。虽然提供了便利,但友元破坏了封装性,应谨慎使用。
39 9
|
4天前
|
存储 编译器 C语言
【C++基础 】类和对象(上)
【C++基础 】类和对象(上)
|
12天前
|
编译器 C++
【C++】string类的使用④(字符串操作String operations )
这篇博客探讨了C++ STL中`std::string`的几个关键操作,如`c_str()`和`data()`,它们分别返回指向字符串的const char*指针,前者保证以'\0'结尾,后者不保证。`get_allocator()`返回内存分配器,通常不直接使用。`copy()`函数用于将字符串部分复制到字符数组,不添加'\0'。`find()`和`rfind()`用于向前和向后搜索子串或字符。`npos`是string类中的一个常量,表示找不到匹配项时的返回值。博客通过实例展示了这些函数的用法。
|
11天前
|
存储
数据存储之数组的特点,长度固定,适应变化需求,集合类特点是空间可变,ArrayList泛型,ArrayList<String> array = new ArrayList<String>()
数据存储之数组的特点,长度固定,适应变化需求,集合类特点是空间可变,ArrayList泛型,ArrayList<String> array = new ArrayList<String>()
|
12天前
|
C++
【C++】string类的使用④(常量成员Member constants)
C++ `std::string` 的 `find_first_of`, `find_last_of`, `find_first_not_of`, `find_last_not_of` 函数分别用于从不同方向查找目标字符或子串。它们都返回匹配位置,未找到则返回 `npos`。`substr` 用于提取子字符串,`compare` 则提供更灵活的字符串比较。`npos` 是一个表示最大值的常量,用于标记未找到匹配的情况。示例代码展示了这些函数的实际应用,如替换元音、分割路径、查找非字母字符等。
|
22天前
|
Java UED
Java中String强转int:一种常见的错误和解决方法
在Java中将非数字字符串转换为整数会导致`NumberFormatException`。要解决这个问题,可以使用`try-catch`捕获异常,正则表达式验证数字格式,或利用异常信息提供错误提示。例如,`Integer.parseInt()`会因遇到非数字字符如`"123abc"`而抛出异常,但通过异常处理或正则`\\d+`可确保安全转换。记得在编程时避免直接强转,以防止程序异常中断。
|
13天前
|
安全 Java
Java基础之StringBuffer
【7月更文挑战第1天】 Java中的`StringBuffer`是线程安全的字符串操作类,适合多线程环境,而`StringBuilder`非线程安全,速度更快,适用于单线程。两者提供`append()`、`insert()`、`delete()`等方法修改字符串,避免了频繁创建新对象的性能问题。在不需要线程安全时,推荐使用`StringBuilder`以提高效率。
12 1
|
14天前
|
安全 Java 索引
带你快速掌握Java中的String类和StringBuffer类(详解常用方法 | 区别 )
带你快速掌握Java中的String类和StringBuffer类(详解常用方法 | 区别 )
|
21天前
|
Java 数据处理 Apache
探讨Java中判断String类型为空和null的方法
探讨Java中判断String类型为空和null的方法
14 1