四时宝库

程序员的知识宝库

C++|包装裸指针、裸数组、字符串成智能指针、array类和string类

我们知道,C编译器出于效率的考虑,将一些初始化、边界检查及善后工作都交给了程序员自己去处理,程序员可以通过自定义机制如结构体、类来封装额外的一些操作,达到自己预想的一些要求(类有自动构造和析构机制)。

1 复合数据类型的封装

我们知道,C编译器中数组做为函数参数时为实现指针传递会退化为一个指针常量,同时会丧失掉数组的长度信息,但如果把一个数组包裹到结构体内呢?

#include <stdio.h>
typedef struct{
 char arr[22];
}String;
void fun(String *str)
{
 printf("sizeof arr is %d\n", (int)sizeof(str->arr));
}
int main()
{
 String s;
 fun(&s);
 return 0;
}

同时,数组封装到结构体内成为结构体成员后可以支持比数组本身更多的操作。

2 数组封装为array类

C++ STL的array类封装一个普通数组,虽如同普通数组一样,被实现为固定大小尺寸,但实现了泛型、下标范围的安全性和元素数量的直接返回。

#include <iostream>
#include <array>
int main ()
{
 std::array<int,10> myarray;
 // assign some values:
 for (int i=0; i<10; i++) myarray.at(i) = i+1;
 // print content:
 std::cout << "myarray contains:";
 for (unsigned int i=0; i<myarray.size(); i++) //如果越界,编译时就可以检测到错误
 std::cout << ' ' << myarray.at(i);
 std::cout << '\n';
 return 0;
}
//myarray contains: 1 2 3 4 5 6 7 8 9 10

3 数组封装为vector类

C++ STL的vector类封装一个普通数组,但实现了泛型、动态大小以及一些安全机制:

template<typename T>
	class Vector {
	T? elem; // pointer to first element
	T? space; // pointer to first unused (and uninitialized) slot
	T? last; // pointer to last slot
	public:
	// ...
	int size(); // number of elements (space-elem)
	int capacity(); // number of slots available for elements (last-elem)
	// ...
	void reserve(int newsz); // increase capacity() to newsz
	// ...
	void push_back(const T& t); // copy t into Vector
	void push_back(T&& t); // move t into Vector
};
template<typename T>
	void Vector<T>::push_back(const T& t)
	{
		if (capacity()<size()+1) // make sure we have space for t
			reser ve(siz e()==0?8:2?siz e()); // double the capacity
		new(space) T{t}; // initialize *space to t
		++space;
	}

使用at()方法而不使用下标操作符可以获得避免越界操作的安全性:

#include <iostream>
#include <vector>
int main ()
{
 std::vector<int> myvector (10); // 10 zero-initialized ints
 // assign some values:
 for (unsigned i=0; i<myvector.size(); i++)
 myvector.at(i)=i;
 std::cout << "myvector contains:";
 for (unsigned i=0; i<myvector.size(); i++)
 std::cout << ' ' << myvector.at(i);
 std::cout << '\n';
 return 0;
}

如果想安全地使用下标,可以实现一个继承自vector的类:

template<typename T>
class Vec : public std::vector<T> {
public:
using vector<T>::vector; // use the constructors from vector (under the name Vec)
T& operator[](int i) // range check
{ return vector<T>::at(i); }
const T& operator[](int i) const // range check const objects; §4.2.1
{ return vector<T>::at(i); }
};

4 C风格字符串封装为string类

C++ STL的string类封装一个普通C风格字符串,但实现了动态大小以及一些安全机制:

#include <iostream>
using namespace std;
#include <string.h>
	class String
{
	char *m_data; //用于保存字符串
	public:
	String(const char *str = NULL); // 普通构造函数
	String(const String &other); // 拷贝构造函数
	~String(void); // 析构函数
	String & operator =(const String &other); // 赋值函数
};
	// String 的普通构造函数
	String::String(const char *str) 
	{
		if(str==NULL)
		{
			m_data = new char[1]; // 若能加 NULL 判断则更好
			*m_data ='\0';
		}
		else
		{
			int length = strlen(str);
			m_data = new char[length+1]; // 若能加 NULL 判断则更好
			strcpy(m_data, str);
		}
	}
// String 的析构函数
String::~String(void) // 3 分
{
	delete [] m_data;
	// 由于m_data 是内部数据类型,也可以写成 delete m_data;
}
// 拷贝构造函数
String::String(const String &other) 
{
	int length = strlen(other.m_data);
	m_data = new char[length+1]; // 若能加 NULL 判断则更好
	strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operator =(const String &other)
{
	if(this == &other) // (1) 检查自赋值
		return *this;
	delete [] m_data; // (2) 释放原有的内存资源
	int length = strlen(other.m_data); //(3)分配新的内存资源,并复制内容 
	m_data = new char[length+1]; // 若能加 NULL判断则更好
	strcpy(m_data, other.m_data);
	return *this; // (4)返回本对象的引用
}
int main ()
{
 	String str("abc");
	return 0;
}

5 裸指针的封装

我们知道,堆上内存比栈内存的空间要大,并且可以动态引用,但随意、没有任何边界检查的使用也会带来较高的安全隐患,为此C++的STL就封装了智能指针,智能指针是存储指向动态分配(堆)对象指针的类。

我们知道,当几个指针指向同一块堆空间时,如何共享?如何delete就是一个大问题(同一空间只能delete一次,实质是将此一空间标识(链接)为可用),C++的STLL的hared_ptr共享对象只能通过复制其值来共享所有权,同时通过拷贝和赋值计数来解决delete的问题,下面来看其实现思想的简易版:

#include<iostream>
#include<stdexcept>
using namespace std;
#define TEST_SMARTPTR
class Stub
{
public:
	void print(){
		cout<<"Stub:print"<<endl;
	}
	~Stub(){
		cout<<"Stub:Destructor"<<endl;
	}
};
template<typename T>
class SmartPtr
{
	T* ptr;
	size_t* pUse;
public:
	SmartPtr(T *p=0):ptr(p),pUse(new size_t(1)){}
	SmartPtr(const SmartPtr&src):ptr(src.ptr),pUse(src.pUse){
		++*pUse;
	}
	SmartPtr&operator=(const SmartPtr&rhs){
		//self-assigning is also right
		++*rhs.pUse;
		decrUse();
		ptr=rhs.ptr;
		pUse=rhs.pUse;
		return *this;
	}
	T* operator->(){
		if(ptr)
			return ptr;
		throw std::runtime_error("access through NULL pointer");
	}
	const T* operator->()const{
		if(ptr)
			return ptr;
		throw std::runtime_error("access through NULL pointer");
	}
	T &operator*(){
		if(ptr)
			return *ptr;
		throw std::runtime_error("dereference of NULL pointer");
	}
	const T &operator*()const{
		if(ptr)
			return *ptr;
		throw std::runtime_error("dereference of NULL pointer");
	}
	~SmartPtr(){
		decrUse();
#ifdef TEST_SMARTPTR
		std::cout<<"SmartPtr:Destructor"<<std::endl;//fortesting
#endif
	}
private:
	void decrUse(){
		if(--*pUse==0){
			delete ptr;
			delete pUse;
		}
	}
};
int main()
{{
	try{
		SmartPtr<Stub>t;
		t->print();
	}catch(const exception&err){
		cout<<err.what()<<endl;
	}
	SmartPtr<Stub>t1(new Stub);
	SmartPtr<Stub>t2(t1);
	SmartPtr<Stub>t3(new Stub);
	t3=t2;
	t1->print();
	(*t3).print();
}//增加一个块结构,为了看到对象离开作用域后的析构结果
	system("pause");
	return 0;
}
/*output:
SmartPtr:Destructor
access through NULL pointer
Stub:Destructor
Stub:print
Stub:print
//以下是离开作用域后的析构
SmartPtr:Destructor
SmartPtr:Destructor
Stub:Destructor
SmartPtr:Destructor
*/

从安全角度出发的一个基本原则就是尽量不使用裸数据结构。C++用类和类库实现了大量的封装,C语言就只能使用结构体和函数自力更生了。

-End-

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接