上一章节我们学习了智能指针,我们可以将对象托管给智能指针管理,通过智能指针来释放资源空间。但是每个资源的释放的方式是不一样的,而我们之前实现的智能指针释放资源的方式单一,那我们该如何解决呢?下面来絮叨絮叨~~
智能指针复习:传送门
class Date
{
public:
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
//std版本:
//传类型
std::unique_ptr<Date> up1(new Date);
//new和delete一定要匹配,不匹配不一定会报错
std::unique_ptr<Date> up2(new Date[10]);
//没报错,但是结果不对
std::unique_ptr<Date> up3((Date*)malloc(sizeof(Date) * 10));
//以读的方式打开了文件,有可能不匹配,有可能报错,有可能不报错,有可能正确释放,有可能不正确释放
std::unique_ptr<FILE> up4((FILE*)fopen("text.cpp", "r"));
return 0;
}
如果只用单纯的智能指针默认的释放空间的方式,就会报错:
原因也很简单,unique_ptr只是用指向资源的指针来释放指向该资源,我们之前在C++内存管理中提到过,delete
在释放空间时一定要匹配,不匹配可能会报错,可能不报错,这和编译器底层实现有关。
那我们的解决办法是用仿函数:
我们来看到标准库中就是用一个仿函数来实现不同资源的释放,那么我们就可以针对不同的资源写一个其对应的仿函数,专门用来释放该资源。
//写一个仿函数
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
template<class T>
struct Free
{
void operator()(T* ptr)
{
cout << "free" << ptr << endl;
free(ptr);
}
};
struct Fclose
{
void operator()(FILE* ptr)
{
cout << "fclose" << ptr << endl;
fclose(ptr);
}
};
int main()
{
//std版本:
//传类型
std::unique_ptr<Date> up1(new Date);
std::unique_ptr<Date, DeleteArray<Date>> up2(new Date[10]);
std::unique_ptr<Date, Free<Date>> up3((Date*)malloc(sizeof(Date) * 10));
std::unique_ptr<FILE, Fclose> up4((FILE*)fopen("text.cpp", "r"));
return 0;
}
此时我们对自己实现的unique_ptr稍微修改一下,加一个带有删除器的功能即可:
namespace YY
{
template<class T>
struct default_delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
//不能拷贝用unique_ptr
template<class T, class D = default_delete<T>>
class unique_ptr
{
public:
//RAII思想
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
//cout << "delete:" << _ptr << endl;
//delete _ptr;
D del;
del(_ptr);//仿函数
_ptr = nullptr;
}
}
//像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
T* _ptr;
};
}
上述unique_ptr
是传类型,而shared_ptr
是支持直接用对象构造的,这和底层实现有关…
类型转换:
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
缺陷:
举个栗子:
int
类型的变量和size_t
类型的对象在一起时int
整形转换成了size_t
无符号整形如果要是在
pos == 0
的地方插入数据的话就相当于头插。这里的end
会走到下标为0
的位置,此时end
再自减就为-1
,因为end
的类型为sizez_t
(无符号整形)就会将-1的补码按照无符号整形解读成一个很大的数即(4294967295)
那么循环永不停止,就是死循环。
如果将
end
的类型设置为int
有符号整形即(int end = ps1->size - 1;)
那当end
为0
时再次自减一次还会发生和上面一样的问题,因为这里发生了算数转换,当int类型的数据和size_t
类型的数据进行比较时,int
类型的数据要转换成size_t
类型的数据这时-1
又被转成很大的数即(4294967295
)。循环不会止。
C风格的转换格式很简单,但是有不少缺点的:
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}
int main()
{
int a = 0;
int* pa = &a;
int aa = reinterpret_cast<int>(pa);
return 0;
}
先来看一段程序:
int main()
{
const int a = 0;
int* pa = (int*)&a;
*pa = 2;
cout << a << endl;
cout << *pa << endl;
return 0;
}
解释:
C++为了规范使用,建议在去掉变量const
属性的时候加上const_cast
。
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl;
return 0;
}
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
注意:
应用场景:
我们用dynamic_cast的方式:
重点:
因为都是指针,所以要是真的强行将父类对象的指针强转成子类对象的指针,也是可以的,这样不安全,所以用dynamic_cast
更好。
代码演示:
class A
{
public:
virtual void f() {}
};
class B : public A
{};
void fun(A* pa)
{
B* ptr = dynamic_cast<B*>(pa);
//指向父类对象,强制转换成指向子类,导致可能会越界
if (ptr)
{
cout << "转换成功" << ptr << endl;
}
else
{
cout << "转换失败" << ptr << endl;
}
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}