通过std::any进行向上转型

4
在 C++ 中,如果 BaseDerived 的基类,则可以将 Derived 的实例传递给接受 Base 的函数。这对于满足 API 和 API 的常见设计模式非常有用。
目前,我遇到了一个情况,我想通过 std::any 进行向上转型。也就是说,我有一个存储 Derived 实例的 std::any,我希望在一个不知道 Derived 存在的 API 函数中将其地址转换为指向 Base 的指针。我的用例是一个运行时反射库,它传递存储反射类型实例的 std::any
我知道通过 std::any 进行向上转型是不可能的,因为 std::any_cast 在转换之前会检查类型,如果类型不匹配,则返回 nullptr,详情请参见 cppreference
但也许有一些变通方法或者聪明的技巧可以使用?感觉这样必须是可以实现的,因为在 C++ 中向上转型是如此普遍,而 std::any 确实 有我需要的地址和类型。
下面是一个代码示例。以下代码会崩溃,因为在 any_function 中,std::any_cast 返回了 nullptr,而在下一行中对其进行了解引用。编译器资源管理器:https://godbolt.org/z/E9sG9G3ff
#include <any>
#include <iostream>

class Base 
{
public:
    double get_val() const {
        return val;
    }

private: 
    double val {1.23};
};

void normal_function(Base const& b){
    std::cout << b.get_val() << std::endl;
};

void any_function(std::any const& b){
    auto* ptr = std::any_cast<Base>(&b);
    std::cout << ptr->get_val() << std::endl;
}

class Derived : public Base {};

int main()
{
    Derived d;
    
    // normal upcasting

    normal_function(d);

    // upcasting thru std::any

    std::any ad = d;
    any_function(ad);
    
    return 0;
}

3
问题在于,从Derived*Base*进行强制转换可能需要指针算术运算,这取决于BaseDerived布局中的位置。因此,在进行转换时,必须意识到两种类型。对于“普通”类之间的多态性,它可以工作,因为调用方知道需要进行强制转换,但是对于std::any来说,不存在这样的机制。你是否必须使用std::any,还是可以使用其他类型? - lorro
1
不,这是不可能的。你应该调查一下是否首先需要std::any。它经常被用作一个逃避方式,一个快速而肮脏的黑客,而不是实现一个完全类型安全的代码。也许有一个更合适的解决方案,可以避免使用std::any - Sam Varshavchik
感谢您的快速回复。这正是我担心的事情。基本上,我需要传递可以存储任意类型的对象,并且由于我喜欢类型安全和STL,所以我选择了std::any而不是类似于void *的东西...但也许std::any太过限制性,我必须想出自己的非STL any-like容器类。 - joergbrech
1
@joergbrech:void*也无法实现您尝试的操作。您不能将Derived*转换为void*,然后再将其转换为Base* - Nicol Bolas
@joergbrech:std::any 几乎从来不是解决任何问题的方案。在大多数情况下,当你想要“任意类型”时,你真正需要的是 std::variant。如果 std::variant 不符合你的需求,那么你设计的东西就有问题,需要重新开始。 - Mooing Duck
我想构建一个非常通用的插件系统,类似于http://www.lucadavidian.com/2021/06/21/simple-runtime-reflection-in-c/。我认为自定义的“Any”类是由于缺乏C++17而产生的,但现在我意识到它为什么是必需的。使用`std::any`,我将无法反射基类的数据成员和函数。我相信这是一种用例,您需要动态类型语言或真正的类型擦除。变体不会很有帮助,因为我无法预先知道所有可能的类型。 - joergbrech
1个回答

7

any是一种类型安全的void*。这意味着它遵循void*的规则。也就是说,如果你将一个T*强制转换为一个void*,那么你唯一能将其转换回来的类型是T*,而不是T的基类U*。它必须精确而且只能是T*

any的情况也是如此。

any(就像使用void*的方式一样)用于A代码需要通过某个中介B与C代码通信的情况。A和C达成了所涉及的类型的共识,但是B不需要知道正在通信的类型是什么(可能是因为B在许多不同的As和Cs对之间进行交易)。

达成的协议是所涉及类型的精确类型。如果A要与只接受基类的代码通信,那么它需要提供(通过存储指向其派生类实例的基类指针)该类型的基类。

现在,你所要求的一般思路并不是不合理的。然而,实施起来…很难。大多数形式的类型擦除可以处理具有类似行为的类型,但被擦除的类型的行为需要在擦除时而不是在使用该功能时已知。如果类型需要转换成某个类型,则需要在将对象添加到any时就知道该类型的确切内容。那将成为假想any类型的接口的一部分,因此它将被编入其代码中。

它不能接受“任何”类型,然后在以后问,“嘿,你能转换成ThisType吗?”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接