NDK13_C++基础:类、构造函数、拷贝构造函数、浅拷贝与深拷贝

本文深入探讨C++中类的定义与使用,包括构造函数、析构函数、拷贝构造函数、深拷贝与浅拷贝的概念及实现。通过实例讲解如何避免浅拷贝带来的问题,以及如何正确使用拷贝构造函数。

NDK开发汇总

一 类

C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,用户定义的类型。

class Student {
	int i;    //默认 private
public:
	Student(int i,int j,int k):i(i),j(j),k(k){};	//构造方法 
	~Student(){};	//析构方法 
private:
	int j;
protected:
	int k;
};

Student student(1,2,3); //调用构造方法 栈
//出方法释放student 调用析构方法

//动态内存(堆)
Student *student = new Student(1,2,3);
//释放
delete student;
student = 0;

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行(不需要手动调用)。

private:可以被该类中的函数、友元函数访问。 不能被任何其他访问,该类的对象也不能访问。

protected:可以被该类中的函数、子类的函数、友元函数访问。 但不能被该类的对象访问。

public:可以被该类中的函数、子类的函数、友元函数访问,也可以被该类的对象访问。

查看内存:

adb shell 进入,命令: dumpsys meminfo 包名

MyTeacher teacher;

  • C++中 这个语句执行完毕,在当前的堆内存内 初始化并且赋值好该对象
  • 在java中执行这个语句,只是开辟了一块内存空间,并没初始化和赋值对象,
    必须用new关键字,来进行初始化和赋值

二 引用

Teacher.h

class MyTeacher
{
public:
	
	~MyTeacher();//析构函数 释放在构造函数里面动态申请的内存 (free)

	MyTeacher(int age,char *name); 

	void setAge(int age);
	int getAge();
	void setName(char *name);
	char* getName();

private:
	int age;
	char *name;
};

Teacher.cpp

#include <Teacher.h>
#include <iostream>

//c++ 标准库的命名空间
using namespace std;

MyTeacher::MyTeacher(int age,char *name):name(name),age(age) {
	cout << " MyTeacher 构造函数  地址:" << this << endl;
}

MyTeacher::~MyTeacher() {
	cout << " MyTeacher 析构函数 地址:" << this << endl;
}
// ::代表限定符
void MyTeacher::setAge(int age) {
	this->age = age;
}

int MyTeacher::getAge() {
	return this->age;
}

void MyTeacher::setName(char *name) {
	this->name = name;
}

char* MyTeacher::getName() {

	return this->name;
}

调用:

#include <Teacher.h>
#include <iostream>

using namespace std;

void fun() {
	MyTeacher teacher = MyTeacher(18, "zhangsan");

	cout << "teacher name:" << teacher.getName() << endl;
	cout << "teacher age:" << teacher.getAge() << endl;
}

void main() {

	fun();

	system("pause");
}

结果:

 MyTeacher 构造函数  地址:00D7F884
teacher name:zhangsan
teacher age:18
 MyTeacher 析构函数 地址:00D7F884

三 指针

void fun() {

	MyTeacher *teacher = new MyTeacher(18, "zangsan");
	cout << "teacher name:" << teacher->getName() << endl;
	cout << "teacher age:" << teacher->getAge() << endl;
	//	delete teacher;
//	teacher = nullptr;
}

结果:

 MyTeacher 构造函数  地址:0113E950
teacher name:zangsan
teacher age:18

没有调用析构函数,需要delete teacher;才会调用

四 拷贝构造函数

使用场景

  1. 值传递
  2. 赋值语句
    MyStudent.h
class MyStudent
{
public:
	MyStudent(int age,char *name,char *teacherName);
	~MyStudent();
	//重写默认的拷贝构造函数
	MyStudent(const MyStudent &student);
public:
	int age;
	char *name;
	char *teacherName;
};

MyStudent.cpp

#include <MyStudent.h>
#include <iostream>

using namespace std;
MyStudent::MyStudent()
{
	cout << " MyStudent 构造函数  地址:" << this << endl;
}

MyStudent::~MyStudent()
{
	cout << " MyStudent 析构函数  地址:" << this << endl;
}

//默认构造函数 浅拷贝
MyStudent::MyStudent(const MyStudent &student) {
	cout << " MyStudent 拷贝构造函数  地址:" << this << endl;
	this->age = student.age;
	this->name = student.name;
	this->teacherName = student.teacherName;
}

调用:

void setFunX(MyStudent student) {
	cout << "setFunc student name:" << student.name << endl;
	cout << "setFunc student age:" << student.age << endl;
	cout << "setFunc student teacherName:" << student.teacherName << endl;
}
void main() {
	MyStudent  student = MyStudent(21, "jack", "Jone");
	setFunX(student);

	system("pause");
}

结果:

 MyStudent 构造函数  地址:008FFE74
 MyStudent 拷贝构造函数  地址:008FFD88
setFunc student name:jack
setFunc student age:21
setFunc student teacherName:Jone
 MyStudent 析构函数  地址:008FFD88

五 浅拷贝与深拷贝

  • 默认的拷贝构造函数是浅拷贝
    浅拷贝:
void copyTest(){
 MyStudent  student = MyStudent(21, "jack", "Jone");
 MyStudent st2 = student;
 }

在这里插入图片描述
浅拷贝出现的问题
修改构造、析构函数

MyStudent::MyStudent(int age, char *name, char *teacherName):age(age)
{
	cout << " MyStudent 构造函数  地址:" << this << endl;
	int len = strlen(name);
	this->name = (char *)malloc(len + 1);

	strcpy(this->name, name);

	len = strlen(teacherName);
	this->teacherName = (char *)malloc(len + 1);
	strcpy(this->teacherName, teacherName);
}

MyStudent::~MyStudent()
{
	cout << " MyStudent 析构函数  地址:" << this << endl;
	free(this->name);
	free(this->teacherName);
}

错误:

C4996	'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.	Project2	c:\users\pf0zybaj\source\repos\project2\project2\src\mystudent.cpp	11

解决:属性 ->C/C+±>命令行 :-D _CRT_SECURE_NO_WARNINGS ->应用

继续运行,打印正常,任意键退出时对报错,free已经释放了,拷贝构造函数执行了,拷贝对象释放野指针释放出错

为了避免浅拷贝出现的问题有两个解决方法:
1 深拷贝

MyStudent::MyStudent(const MyStudent &student) {
	cout<<"MyStudent 深拷贝构造函数 地址:" << this << endl;
	int len = strlen(student.name);
	this->name = (char *)malloc(len + 1);

	strcpy(this->name, student.name);

	len = strlen(student.teacherName);
	this->teacherName = (char *)malloc(len + 1);
	strcpy(this->teacherName, student.teacherName);
}

2 私有化拷贝构造函数(不常用)

六 Demo

NDK13_C++基础:构造函数、拷贝构造函数、浅拷贝

<think> 我们正在分析一个崩溃问题,崩溃发生在`std::__ndk1::__hash_table`的`rehash`函数中,同时涉及`std::atomic<bool>`的使用(`Fork::support_enabled_`的加载)和`std::shared_ptr`的构造(`make_shared`)。我们首先需要理解这些组件在崩溃时可能如何交互。 首先,我们注意到崩溃的堆栈涉及两个主要部分: 1. `Fork::support_enabled_.load(std::memory_order_relaxed)` 2. `std::__ndk1::__hash_table`的`rehash`操作(位于`__hash_table`文件的2375行) 3. `std::shared_ptr`的`make_shared`操作(用于`aqua::app_communication::GrpcServerSignal`) 我们已知的信息: - `Fork::support_enabled_`是一个原子布尔变量,初始为`false`。使用`memory_order_relaxed`加载,这意味着它只保证原子性,不提供同步或顺序保证。 - `__hash_table`的`rehash`操作:当哈希表需要扩容时会重新哈希所有元素,这通常是一个耗时的操作,并且需要保证线程安全。 - `shared_ptr`的`make_shared`:动态分配对象并构造一个共享指针。 崩溃可能发生在多线程环境中,其中一个线程正在修改哈希表(例如插入元素导致rehash),而另一个线程同时访问哈希表或进行其他操作。 我们考虑以下可能的原因: 1. **原子变量使用`memory_order_relaxed`导致的问题**: - `support_enabled_.load(std::memory_order_relaxed)`用于检查是否启用了fork支持。由于使用relaxed内存序,它不会其他内存操作同步。如果另一个线程在修改这个标志(例如设置为`true`)的同时,有线程在读取,虽然读取的原子性保证了不会读到中间值(只能是`true`或`false`),但是读取线程可能看不到最新的值(尽管对于布尔值,这通常不是问题,因为只有两个值)。然而,这里更大的问题是,这个标志的加载可能用于控制某些fork相关的行为,而在fork过程中操作多线程数据结构(如哈希表)是危险的。 2. **fork多线程的交互问题**: - 在支持fork的系统中,如果程序在多个线程运行时调用fork,那么新进程中只有调用fork的线程被复制,其他线程都会消失。但是,在fork之前,其他线程可能正持有锁(如哈希表内部的锁)或处于不一致状态(如正在rehash),这将导致子进程中的数据结构处于损坏状态。 - 如果`Fork::support_enabled_`被设置为`true`,那么程序可能在某个时刻准备fork。但即使没有fork发生,如果哈希表的操作没有考虑到fork安全,也可能出现问题。 3. **哈希表的rehash操作不是线程安全的**: - 虽然标准库的容器通常不是线程安全的,但这里我们注意到崩溃发生在`rehash`函数中。如果多个线程同时操作同一个哈希表(例如一个线程在插入元素触发rehash,另一个线程也在插入或读取),那么就会导致数据竞争,进而崩溃。 4. **shared_ptr的构造哈希表操作的竞争**: - 堆栈中同时出现了`shared_ptr`的构造和哈希表的rehash。可能的情况是,在哈希表中存储的是`shared_ptr`,当插入一个新的`shared_ptr`时,触发了rehash。而`make_shared`会分配内存并构造对象,这期间如果另一个线程正在rehash,并且哈希表没有适当的同步,就会导致问题。 5. **Android NDK环境下的特殊问题**: - 堆栈路径指向了NDK中的头文件,表明这是在Android平台上运行的。NDKC++标准库实现(libc++)可能有特定的行为。 结合堆栈信息,我们特别关注`__hash_table:2375`行(在rehash函数中)的崩溃。我们可以查找libc++的源代码来了解该行代码在做什么。 假设我们无法直接获取NDK中该行代码,但通常rehash函数会重新分配桶数组,并将所有元素重新哈希到新桶中。在这个过程中,如果其他线程同时修改哈希表(例如插入、删除),那么就会导致竞争条件。 另外,我们注意到`Fork::support_enabled_`的加载出现在崩溃堆栈中,说明在崩溃发生前,程序检查了这个标志。这可能是崩溃发生的前置条件,或者崩溃有间接关系。 一个合理的推测是:程序在某个时刻启用了fork支持(将`support_enabled_`设为true),然后在一个多线程环境中,某个线程调用了fork,而此同时,另一个线程正在执行哈希表的rehash操作。在子进程中,rehash操作可能因为锁被永久占用(因为持有锁的线程在子进程中不存在)或数据结构处于不一致状态而导致后续操作崩溃。 但是,我们并没有在堆栈中看到fork调用,所以也可能是主进程中的问题。另一种可能是:即使没有fork,由于`support_enabled_`被设置为true,程序可能安装了一些fork处理程序(例如通过`pthread_atfork`),这些处理程序在fork发生时会被调用。如果这些处理程序哈希表的操作存在竞争,也可能导致问题。 然而,我们更倾向于认为崩溃是由于多线程同时访问哈希表导致的。因为`rehash`操作通常不是线程安全的,而我们在堆栈中看到`rehash`,说明它正在执行中,此时如果有另一个线程也操作哈希表,就会崩溃。 那么为什么堆栈中会有`Fork::support_enabled_.load`?这可能是因为在哈希表的操作过程中(比如在插入元素时),会检查这个标志以确定是否进行某些fork相关的操作(如加锁)。如果我们查看libc++的源代码,我们可能会发现,在rehash过程中会检查一些全局设置,其中可能包括这个fork支持标志。 但是,根据提供的代码片段,我们没有看到哈希表操作和`Fork`之间的直接联系。因此,我们需要更多的上下文。 考虑到崩溃发生在`__hash_table:2375`行,我们可以假设该行代码似于: ```cpp // 在rehash函数中 for (size_type __i = 0; __i < __bc; ++__i) { // ... 重新哈希每个桶的元素 } ``` 或者是对某个迭代器的操作。常见的崩溃原因可能是: - 迭代器失效(在rehash过程中,其他线程修改了桶) - 访问了无效的内存(如已经被释放的节点) 由于哈希表中存储的是`shared_ptr`,我们还需要考虑引用计数的原子操作。`shared_ptr`的引用计数是原子操作,通常不会引起问题,但是如果在rehash过程中,某个`shared_ptr`被另一个线程重置,那么当rehash访问该节点时,可能节点已经被释放(如果引用计数降为0)。 但是,在rehash过程中,哈希表会持有所有元素的引用(即增加引用计数)吗?通常不会。rehash操作只是重新组织表结构,并不会改变元素的所有权。所以,如果另一个线程删除了一个元素(导致其引用计数降为0),那么rehash过程中访问这个元素就会导致野指针。 因此,根本原因可能是:这个哈希表不是线程安全的,而多个线程在没有同步的情况下同时访问它(一个线程rehash,另一个线程删除元素)。 解决方案: 1. 确保对哈希表的操作(插入、删除、查找)都是互斥的,例如使用互斥锁。 2. 检查`Fork::support_enabled_`的使用:如果它确实fork相关,确保在fork时所有线程都处于安全状态(例如,在fork前停止所有其他线程,或使用`pthread_atfork`注册处理程序来加锁)。 但是,为什么`Fork::support_enabled_`的加载会出现在堆栈中?可能是在每次操作哈希表之前,程序会检查这个标志以决定是否进行额外的同步(例如,如果启用了fork支持,则使用锁)。如果这个检查是使用relaxed序,那么它可能没有正确同步。 例如: ```cpp if (Fork::support_enabled_.load(std::memory_order_relaxed)) { // 使用锁或其他同步机制 } ``` 但是,由于是relaxed序,可能在标志被设置为true之后,这个线程并没有看到,因此跳过了同步,而此时另一个线程正在fork,导致问题。 然而,这个标志通常只会在程序初始化时设置一次(从false变为true),之后不再修改。所以,使用relaxed序读取是安全的(因为一旦看到true,就永远为true)。但是,在设置之前和设置之后,线程需要正确同步以看到新的值。由于设置操作(store)可能使用更严格的内存序(如release),而读取使用acquire(但这里使用了relaxed),所以可能有些线程在设置后很长时间内仍然看到false。不过,这通常不会导致崩溃,只是导致没有使用fork支持的同步机制。 所以,更可能的原因还是哈希表本身的线程不安全。 综上所述,崩溃原因可能是多线程竞争访问哈希表(一个线程触发rehash,另一个线程同时修改哈希表)。而`Fork::support_enabled_`的加载只是崩溃路径上的一个操作,可能不是根本原因。 建议: 1. 检查代码,确保所有对哈希表的操作都进行了适当的同步(如使用互斥锁)。 2. 如果确实需要支持fork,请确保在fork时没有线程正在操作哈希表(通过`pthread_atfork`在fork前加锁,fork后解锁)。 由于我们没有完整的代码和堆栈,以上分析基于典型情况。 注意:在Android NDK的libc++中,`__hash_table`的实现可能包含一些调试或特定于平台的代码,所以崩溃的具体位置(2375行)需要结合源码分析。但无论如何,多线程竞争是常见问题。 因此,我们得出结论:崩溃的根本原因可能是哈希表在多线程环境下未加锁操作导致的竞争条件。需要给哈希表加上锁。 </think>### 崩溃分析:原子变量哈希表rehash的并发冲突 #### 1. **崩溃场景还原** ```mermaid sequenceDiagram Thread A->>+HashTable: 执行rehash操作 Thread B->>+AtomicVar: 读取support_enabled_(relaxed序) Thread B->>+HashTable: 插入新元素(触发shared_ptr构造) Thread A-->>-HashTable: 正在rehash时内存结构变化 Thread B--xHashTable: 访问失效内存导致崩溃 ``` #### 2. **核心崩溃原因** 1. **内存序使用不当** `support_enabled_.load(std::memory_order_relaxed)` 的宽松内存序无法哈希表操作建立同步关系: $$ \text{同步缺失} \implies \text{数据竞争} $$ - relaxed序不保证操作顺序,导致哈希表状态可见性无法保障 - 当rehash修改桶数组时,其他线程可能看到中间状态 2. **rehash并发问题** 哈希表rehash过程涉及关键操作: ```cpp // __hash_table:2375 附近典型实现 void rehash(size_type __n) { __pointer_allocator& __npa = __bucket_list_.get_allocator(); __bucket_list_.reset(); // 释放旧桶数组 __create_buckets(__n); // 分配新桶数组 // ... 元素迁移 ... } ``` - 旧桶数组释放后,其他线程访问会导致野指针 - 新桶数组未完成初始化时访问引发内存错误 3. **shared_ptr构造竞争** `make_shared`在构造对象时: ```cpp std::__ndk1::shared_ptr<GrpcServerSignal>::make_shared() ``` - 涉及内存分配和引用计数初始化 - 若rehash并发执行,可能访问已释放的哈希表节点 #### 3. **根本原因定位** | 因素 | 影响 | 解决方案方向 | |------|------|------------| | 宽松内存序 | 丧失同步保障 | 升级内存序 | | 非原子rehash | 结构非一致性 | 增加锁机制 | | shared_ptr构造 | 内存分配竞争 | 分离资源管理 | #### 4. **修复方案** 1. **内存序强化** 修改原子标志加载方式: ```cpp // 原代码 (不安全) support_enabled_.load(std::memory_order_relaxed); // 修改后 (建立同步) if(support_enabled_.load(std::memory_order_acquire)) { // 受保护操作 } ``` - `acquire`序可能的`release`存储配对 - 建立 happens-before 关系:$ \text{store-release} \rightarrow \text{load-acquire} $ 2. **rehash并发控制** 添加细粒度锁: ```cpp void rehash(size_type __n) { std::lock_guard<std::mutex> lock(rehash_mutex_); // ... 原有rehash逻辑 ... } ``` - 确保桶数组重置和重建操作的原子性 - 插入/删除操作共用同一锁 3. **shared_ptr构造隔离** 预分配策略: ```cpp // 在rehash前完成对象构造 auto new_obj = std::make_shared<GrpcServerSignal>(); { std::lock_guard lock(table_mutex); table.insert({key, new_obj}); } ``` #### 5. **崩溃路径验证** $$ \text{并发rehash} + \text{relaxed序访问} \rightarrow \text{访问失效桶指针} \rightarrow \text{SIGSEGV} $$ 典型崩溃栈: ``` #0 0x0000 in __hash_table::rehash() at line 2375 #1 0x0000 in Fork::check_support() (调用support_enabled_.load(relaxed)) #2 0x0000 in shared_ptr<GrpcServerSignal>::make_shared() #3 0x0000 in 业务逻辑插入操作 ``` 证明原子变量访问触发了哈希表的不安全操作[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值