C++基础:Any类的简化版代码实现

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 两个类型,用以处理不同的类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值