2024/11/15-2024/11/18:
在线程池编写阶段接触到Any类的时候发现这个类的实现有很多的细节,故在此梳理。
一、目标功能:
目标功能很简单:
1、需要能接收各种类型的值,且旧对象能赋予不同类型的新值,即能做到:
Any a = 1;
a = std::string("hello");
2、此外,可以通过类似于std::any_cast<T>的方式来访问当前存放值,即:
Any a = 1;
std::cout << a.cast_<int>() << std::endl;
二、思考与实践
在整个实现过程中,令我最困扰的部分在于:这样看起来是可以的,实际上为什么不行?接下来我会逐步呈现自己的实现思路:
2.1 易错解:直接使用类模板
通过Any的特性,我们很容易想到需要通过模板类来实现它,一开始大部分人可能会想到这样写:
template<typename T>
class Any{
public:
Any() = default;
~Any() = default;
Any& operator=(Any&) = default;
Any(T data):
_data(data) {}
T cast_() {
return _data;
}
private:
T _data;
};
很遗憾,当我们把这个思路写出来的时候就会发现问题,显然它两个功能都没有实现。测试代码如下:
int main() {
Any a = 1;
cout << a.cast_() << endl;
a = string("hello");
}
结果:
error: no match for 'operator=' (operand types are 'Any<int>' and 'std::string' {aka 'std::__cxx11::basic_string<char>'})
32 | a = string("hello");
| ^
note: candidate: 'constexpr Any<T>& Any<T>::operator=(Any<T>&&) [with T = int]'
19 | Any& operator=(Any&&) = default;
| ^~~~~~~~
note: no known conversion for argument 1 from 'std::string' {aka 'std::__cxx11::basic_string<char>'} to 'Any<int>&&'
19 | Any& operator=(Any&&) = default;
| ^~~~~
这与类模板的特性有关,当 Any a = 1 执行时,模板类Any已实例化为类Any<int>,一个实例化的模板类已经无法接收其他类型的对象,如下图所示:

因此,只使用一个简单的类模板来实现Any的这两个功能是完全行不通的。
2.2 基类指针指向派生类模板
上面这种思路行不通,基本也就说明了一个很重要的问题:
如果要实现Any类,Any类本身不可以是模板类。显然只要把Any作为模板类型,那就一定会出现上面这样的错误。
好了,现在我们知道Any类不能是一个模板类了,但是在C++中如果想做到类型无关这种事,那就基本绕不开模板这两个字。那么,这就意味着Any类中必须有一个或几个成员需要采用模板的方式实现。现在梳理一下思路:
1、Any不可以是模板类,不然会出现和上面一样的错误;
2、私有的成员变量 data 的类型也不能由模板决定,但它与模板一定有所联系;
3、cast_<T>函数必须是模板函数,由调用者显式实例化,否则面对Any类中可能存放的各类不同值,函数将无法决定输出。
其中,1和3是显而易见的,2最让人困惑。但联系一下C++有关于多态、继承、虚函数的相关知识,答案就只有一个了:
Any类私有的成员变量,必须是一个基类指针,该指针指向由派生模板类在堆上实例化的对象。
用这种基类指向派生类的办法,我们可以做到让一个类的类型指向其他任意的类型。
代码如下:
// Any类型:可以接收任意数据类型
class Any {
public:
Any() = default;
~Any() = default;
// 禁止左值的拷贝构造和赋值(unique_ptr)
Any(const Any&) = delete;
Any& operator=(const Any&) = delete;
// 允许右值的拷贝构造和赋值
Any(Any&&) = default;
Any& operator=(Any&&) = default;
// 该构造函数能让Any类型接收任意其他类型的数据
template<typename T>
Any(T data) : base_(std::make_unique<Derive<T>>(data)) {}
// 把Any对象中存储的data数据提取出来
template<typename T>
T cast_() {
// 怎么从base_中找到Derive对象再取出data成员变量
// 基类指针 -> 派生类指针 RTTI
// 判断<T>是否与类成员base中取得的数据类型相同
Derive<T>* pd = dynamic_cast<Derive<T>*>(base_.get());
if (pd == nullptr) {
std::cerr << "type is unmatch!" << std::endl;
}
return pd->data_;
}
private:
// 基类类型
class Base {
public:
virtual ~Base() = default;
};
// 派生类型
template<typename T>
class Derive : public Base {
public:
explicit Derive(T data) : data_(std::move(data)) {}
T data_;
};
// 定义一个基类指针
std::unique_ptr<Base> base_;
};
贴一下测试代码:
int main() {
Any a = 1;
cout << a.cast_<int>() << endl;
a = string("abc");
cout << a.cast_<string>() << endl;
}
跑测试结果:
1
abc
复现成功。
如果想了解类模板的潜在工作方式,可以用在线网站和插件查看一下模板的具体展开,我这里用的是C++ Insights,这里贴一下:
#include <iostream>
#include <string>
#include <memory>
#include <utility>
using namespace std;
class Any
{
public:
inline constexpr Any() /* noexcept */ = default;
inline ~Any() noexcept = default;
// inline Any(const Any &) = delete;
// inline Any & operator=(const Any &) = delete;
inline Any(Any &&) /* noexcept */ = default;
inline Any & operator=(Any &&) noexcept = default;
template<typename T>
inline Any(T data)
: base_(std::make_unique<Derive<T> >(data))
{
}
/* First instantiated from: insights.cpp:59 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Any<int>(int data)
: base_{std::unique_ptr<Base, std::default_delete<Base> >(std::make_unique<Derive<int> >(data))}
{
}
#endif
/* First instantiated from: insights.cpp:61 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline Any<std::basic_string<char> >(std::basic_string<char> data)
: base_{std::unique_ptr<Base, std::default_delete<Base> >(std::make_unique<Derive<std::basic_string<char> > >(data))}
{
}
#endif
template<typename T>
inline T cast_()
{
Derive<T> * pd = dynamic_cast<Derive<T> *>(this->base_.get());
if(operator==(pd, nullptr)) {
std::operator<<(std::cerr, "type is unmatch!").operator<<(std::endl);
}
return pd->data_;
}
/* First instantiated from: insights.cpp:60 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline int cast_<int>()
{
Derive<int> * pd = dynamic_cast<Derive<int> *>(this->base_.get());
if(pd == nullptr) {
std::operator<<(std::cerr, "type is unmatch!").operator<<(std::endl);
}
return pd->data_;
}
#endif
/* First instantiated from: insights.cpp:62 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline std::basic_string<char> cast_<std::basic_string<char> >()
{
Derive<std::basic_string<char> > * pd = dynamic_cast<Derive<std::basic_string<char> > *>(this->base_.get());
if(pd == nullptr) {
std::operator<<(std::cerr, "type is unmatch!").operator<<(std::endl);
}
return std::basic_string<char>(pd->data_);
}
#endif
private:
class Base
{
public:
inline virtual ~Base() noexcept = default;
// inline constexpr Base() noexcept = default;
};
template<typename T>
class Derive : public Base
{
public:
inline explicit Derive(T data)
: data_(std::move(data))
{
}
T data_;
};
/* First instantiated from: type_traits:1417 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Derive<int> : public Base
{
public:
inline explicit Derive(int data)
: Base()
, data_{std::move(data)}
{
}
int data_;
// inline virtual ~Derive() noexcept = default;
};
#endif
/* First instantiated from: type_traits:1417 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class Derive<std::basic_string<char> > : public Base
{
public:
inline explicit Derive(std::basic_string<char> data)
: Base()
, data_{std::basic_string<char>(std::move(data))}
{
}
std::basic_string<char> data_;
// inline virtual ~Derive() noexcept = default;
};
#endif
std::unique_ptr<Base, std::default_delete<Base> > base_;
};
int main()
{
Any a = Any(1);
std::cout.operator<<(a.cast_<int>()).operator<<(std::endl);
a.operator=(Any(std::basic_string<char>(std::basic_string<char>("abc", std::allocator<char>()))));
std::operator<<(std::cout, a.cast_<std::basic_string<char> >()).operator<<(std::endl);
return 0;
}
可以看到派生类模板以及cast函数实例化成了 string 和 int 两个类型,用以处理不同的类型。
2810

被折叠的 条评论
为什么被折叠?



