面试中被问到过的一个问题,如何实现一个类它的对象可以接收所有类型的数据,查了一些资料后发现这个东西其实就是std::any不过我的编译器不太支持(版本原因),所以查了一些资料手动实现了一下。
原理的话很简单,你只需要存储数据的地址和数据类型然后访问的时候强转再解引用就行了,过程很简单,但是类型这里会有各种各样的问题,单单是数据类型就有很多情况,所以整体设计使用了模板结合一些必要的检测完成
#include<iostream>
#include<typeinfo>
#include<stdexcept>
#include<errno.h>
#include<memory>
namespace Any {
//定义异常
class bad_cast :public std::exception {
public:
const char* what() const noexcept override {
return "bad_cast error";
}
};
//标识对象可以安全复制,确保默认提供的拷贝可以使用
class copy_able {
protected:
copy_able(const copy_able&) = default;
copy_able& operator=(const copy_able&) = default;
copy_able() = default;
~copy_able() = default;
};
class any :public copy_able {
struct type_base;//前置声明
using Dataptr = std::unique_ptr<type_base>;//包装数据指针
template<typename T>
using decay = typename std::decay<T>::type;//折叠掉类型的描述符
struct type_base//抽象存储数据类型
{
type_base() = default;
virtual ~type_base() = default;
virtual const std::type_info& get_typeinfo()const = 0;
virtual Dataptr clone() const = 0;
};
//匹配所有接收到的数据类型,并提供构造方法
template<typename T>
struct type_impl :public type_base
{
explicit type_impl(const T& data) :data_(data) {};
explicit type_impl(T&& data) :data_(std::move(data)) {}
template<class...Args>
explicit type_impl(Args&&...args) :data_(std::forward<Args>(args)...) {}
auto get_typeinfo() const->const std::type_info & override {
return typeid(T);
}
auto clone()const -> Dataptr override {
return std::unique_ptr<type_impl>(new type_impl(data_));
}
T data_;
};
public:
any() = default;
any(const any& other) {
if (other.m_data) {
m_data = other.m_data->clone();
}
}
any(any&& other)noexcept :m_data(std::move(other.m_data)) {}
template<class T, typename std::enable_if<!std::is_same<any, decay<T>>::value, bool>::type = true>//剔除对any相关类型的匹配
any(T&& data) : m_data(new type_impl<decay<T>>(std::forward<T>(data))) {}
any& operator=(const any& other) {
if (other.m_data) {
m_data = other.m_data->clone();
}
return *this;
}
any& operator=(any&& other)noexcept {
m_data.swap(other.m_data);//交换而非移动,开销小,同时因为使用的是交换所以没有必要判断资源是否相同
return *this;
}
template<typename T, typename std::enable_if<!std::is_same<any, decay<T>>::value, bool>::type = true>
any& operator=(T&& data) {
m_data.reset(new type_impl<decay<T>>(std::forward<T>(data)));
return *this;
}
~any() = default;
auto has_value()const ->bool {
return m_data.operator bool();
}
auto type()const ->const std::type_info& {
return has_value() ? m_data->get_typeinfo() : typeid(void);
}
void reset()noexcept {//将当前对象置空
m_data.reset();
}
void swap(any& other) {//注意这里是调用unique_ptr提供的接口不会递归
m_data.swap(other.m_data);
}
template<class T, class...Args>
void emplace(Args...args) {
m_data.reset(new type_impl<decay<T>>(std::forward<Args>(args)...));
}
template<typename T>
const T& cast()const {//返回const修饰的资源
if (check_type<T>() && check_not_null()) {
return static_cast<const type_impl<T>*>(m_data.get())->data_;
}
throw bad_cast();
}
template<typename T>
T& cast() {
if (check_type<T>() && check_not_null()) {
return static_cast<type_impl<T>*>(m_data.get())->data_;
}
throw bad_cast();
}
private:
template<typename T>
auto check_type() const -> bool {
if (typeid(T).hash_code() == m_data->get_typeinfo().hash_code()) return true;
return false;
}
bool check_not_null()const {
if (!m_data) {
fprintf(stderr, "in file %s:%d\n nullptr data", __FILE__, __LINE__);
return false;
}
return true;
}
Dataptr m_data;
};
//下面是一些外部接口,不多说了
inline void swap(any& l, any& r) {
l.swap(r);
}
template<class T>
const T& any_cast(const any& any) {
return any.template cast<T>();
}
template<class T>
T& any_cast(const any& any) {
return any.template cast<T>();
}
}
技术点
策略模式(Strategy Pattern):any 类使用了虚函数和多态性,将不同类型的数据存储和操作抽象成了一个接口,并针对不同的数据类型实现了不同的策略。具体来说,在 any 类中,type_base 类是一个抽象接口,而 type_impl 类是其具体策略的实现。
RAII(资源获取即初始化):使用了 std::unique_ptr 来管理动态内存资源,实现了自动资源管理的机制,确保在对象生命周期结束时释放资源,避免了内存泄漏。
模板方法模式(Template Method Pattern):any 类中的 emplace 函数使用了模板方法,允许在不同的具体类型下执行不同的实现细节,同时通过参数推导实现了通用性。