(译)std::any原理以及一个利用std::any的接口实现
06 Feb 2021
|
|
原文 https://www.fluentcpp.com/2021/02/05/how-stdany-works/
https://www.fluentcpp.com/2021/01/29/inheritance-without-pointers/
我加了个人的理解,大家英文好的去看原文
首先 any肯定是记录了type信息的,所以实现可能是这个样子
struct any
{
void* data_;
std::type_info const& type_;
template<typename T>
explicit any(T&& value)
: data_{new T{std::forward<T>(value)}}
, type_{typeid(T)}
{
}
};
template<typename T>
T& any_cast(any& aAny)
{
if (typeid(T) == aAny.type_)
{
return *static_cast<T*>(aAny.data_);
}
else
{
throw std::bad_any_cast{};
}
}
保存了type_info带来了问题,首先,强依赖type_info,typo_info很多场景是不需要的,没必要保存
其次,any拷贝存在问题,只知道type_info而不知道真正的类型T,无法转换。我们需要一个真正的静态的类型
如何不保存类型,但是能记住类型?
以前有过类似的type-map的需求,是引入一个映射
这里不需要map,map太重,那么还有什么能记住类型?lambda
struct any
{
void* data_;
std::type_info const& (*getType_)();
void* (*clone_)(void* other);
template<typename T>
explicit any(T&& value)
: data_{new T{std::forward<T>(value)}}
, getType_{[]() -> std::type_info const& { return typeid(T); }}
, clone_([](void* otherData) -> void* { return new T(*static_cast<T*>(otherData)); })
{
}
};
这里用函数指针来返回类型信息,类型信息用lambda保存就可以了,首先,原有的type_info保留,其次,clone函数指针提供静态的T,支持拷贝构造
any(any const& other)
: data_(other.clone_(other.data_))
, getType_(other.getType_)
, clone_(other.clone_)
{
}
那同理,函数的析构也要塞个lambda
struct any
{
void* data_;
std::type_info const& (*getType_)();
void* (*clone_)(void* otherData);
void (*destroy_)(void* data);
template<typename T>
explicit any(T&& value)
: data_{new T{std::forward<T>(value)}}
, getType_{[]() -> std::type_info const&{ return typeid(T); }}
, clone_([](void* otherData) -> void* { return new T(*static_cast<T*>(otherData)); })
, destroy_([](void* data_) { delete static_cast<T*>(data_); })
{
}
any(any const& other)
: data_(other.clone_(other.data_))
, getType_(other.getType_)
, clone_(other.clone_)
, destroy_(other.destroy_)
{
}
~any()
{
destroy_(data_);
}
};
到此为止就大概讲完了,具体的实现见参考链接,还有SSO优化没有讲
回到主题,如何实现不依赖继承的接口
先放一个继承的例子
struct ICalculator
{
virtual double compute(int input) const = 0;
virtual void log(int input, int output) const = 0;
virtual ~ICalculator() {};
};
struct SmallCalculator : ICalculator
{
int compute(int input) const override
{
return input + 2;
}
void log(int input, int output) const override
{
std::cout << "SmallCalculator took an input of " << input << " and produced an output of " << output << '\n';
}
};
struct BigCalculator : ICalculator
{
int compute(int input) const override
{
return input * 5 ;
}
void log(int input, int output) const override
{
std::cout << "BigCalculator took an input of " << input << " and produced an output of " << output << '\n';
}
};
std::vector<std::unique_ptr<ICalculator>> calculators;
calculators.push_back(std::make_unique<BigCalculator>());
calculators.push_back(std::make_unique<SmallCalculator>());
std::unique_ptr<ICalculator> createCalculator()
{
return std::make_unique<BigCalculator>();
}
非常常规啊,没啥说的
如何用std::any实现类似的效果?
类似any实现的方法,用lambda把类型包起来,构造的时候传进来
struct Calculator
{
public:
template<typename ConcreteCalculator>
Calculator(ConcreteCalculator &&calculator)
: storage{std::forward<ConcreteCalculator>(calculator)}
, getter{ [](std::any &storage) -> ICalculator& { return std::any_cast<ConcreteCalculator&>(storage); } }
{}
ICalculator *operator->() { return &getter(storage); }
private:
std::any storage;
ICalculator& (*getter)(std::any&);
};
利用any来存,并且利用了any构造的那种手法初始化getter
这种场景下 operator ->实际上是值语义而不是指针语义。也是一个比较有意思的点(类似std::optional)
using Calculator = Implementation<ICalculator>;
使用起来干净了许多,直接赋值即可
参考链接
- std::any gcc实现 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any