原文 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