16

在我正在工作的代码库中,我们使用std::any而不是void*通过一些通用的非模板代码传递类。具体来说,我们使用 Visual Studio 2019、它的编译器和标准库。

为了可视化std::any,微软已经给出了一个 natvis:

  <Type Name="std::any">
      <Intrinsic Name="has_value"   Expression="_Storage._TypeData != 0"/>
      <Intrinsic Name="_Rep"        Expression="_Storage._TypeData &amp; _Rep_mask"/>
      <Intrinsic Name="type"        Expression="(const type_info*)(_Storage._TypeData &amp; ~_Rep_mask)"/>
      <Intrinsic Name="_Is_trivial" Expression="has_value() &amp;&amp; _Rep() == 0"/>
      <Intrinsic Name="_Is_big"     Expression="has_value() &amp;&amp; _Rep() == 1"/>
      <Intrinsic Name="_Is_small"   Expression="has_value() &amp;&amp; _Rep() == 2"/>
      <DisplayString Condition="!has_value()">[empty]</DisplayString>
      <DisplayString Condition="_Is_trivial() || _Is_small()">[not empty (Small)]</DisplayString>
      <DisplayString Condition="_Is_big()">[not empty (Large)]</DisplayString>
      <Expand>
          <Synthetic Name="has_value">
              <DisplayString>{has_value()}</DisplayString>
          </Synthetic>
          <Synthetic Name="type" Condition="has_value()">
              <DisplayString>{type()}</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_trivial()">
              <DisplayString>(Small/Trivial Object)</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_small()">
              <DisplayString>(Small Object)</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_big()">
              <DisplayString>(Dynamic Allocation)</DisplayString>
          </Synthetic>
      </Expand>
  </Type>

然而,这最终显示给我们(Small Object)而不是std::string我们存储在其中的内容。我已经设法用一些额外的行来扩展它以获取指向数据的指针:

          <Item Name="[castable_ptr]" Condition="_Is_trivial()">(void*)(&amp;_Storage._TrivialData)</Item>
          <Item Name="[castable_ptr]" Condition="_Is_small()">(void*)(&amp;_Storage._SmallStorage._Data)</Item>
          <Item Name="[castable_ptr]" Condition="_Is_big()">(void*)(_Storage._BigStorage._Ptr)</Item>

但是,这会将数据显示为void*,您必须手动将其转换为实际类型的指针std::string*。但是,此std::any实现/可视化还附带一个std::type_info. (参见字段:类型)知道我们拥有哪种底层类型。

有没有办法使用它std::type_info,以便(void*)可以将其替换为实际存储类型的强制转换?

编辑: Visual Studio 为该类型提供的信息示例:{mydll.dll!class std::tuple<__int64,double,double,double> 'RTTI Type Descriptor'} {...} 当将地址显式转换为 std::type_info* 时,我可以_Data在包含_DecoratedName( .?AV?$tuple@_JNNN@std@@) 和_UndecoratedName( nullptr) 的调试器中访问。不幸的是,我似乎不知道如何编写一个利用这些信息的演员表。

4

2 回答 2

1

我不知道上下文(实施限制)。如果使用std::any被确定为一个强有力的先决条件(我可以理解)并且在微软改进本机 vizualizer 之前,下面可能是一个战术上的快速胜利。

在 natvis xml 配置文件中:

<Synthetic Name="[representation]" Condition="_Is_trivial()">
    <!--<DisplayString>(Small/Trivial Object)</DisplayString>-->
    <DisplayString>{_Storage._TrivialData._Val}</DisplayString>
</Synthetic>
<Synthetic Name="[representation]" Condition="_Is_small()">
    <!--<DisplayString>{*displayStdAnyContent(*this)}</DisplayString>-->
    <Expand>
        <Item Name="[value]">*displayStdAnyContent(*this)</Item>
    </Expand>
</Synthetic>

<Type Name="AnyCastedValue&lt;*&gt;">
    <DisplayString>{value}</DisplayString>
    <Expand>
        <Item Name="[value]">value</Item>
    </Expand>
</Type>

C++

//----------------------------------------------------------------------------------
class IAnyValue
{
public:
    virtual ~IAnyValue() = default;
};

template <typename T>
struct AnyCastedValue final :public IAnyValue
{
    AnyCastedValue(const T& pi_value) :value(pi_value) {}

    T value;
};

struct NonCastableValue final :public IAnyValue
{
    static constexpr auto message = "I'm not castable! Please register my type!";
};

//----------------------------------------------------------------------------------
class IAnyCaster
{
public:
    virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const = 0;
    virtual ~IAnyCaster() = default;
};

template <typename T>
class AnyCaster final :public IAnyCaster
{
public:
    AnyCaster() = default;

    virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const override
    {
        return std::make_unique<AnyCastedValue<T>>(std::any_cast<T>(pi_any));
    }
};
    
//----------------------------------------------------------------------------------
class AnyCasterService final :public std::unordered_map<std::string, std::unique_ptr<IAnyCaster>>
{
public:
    AnyCasterService()
    {
        //here we register the types you would like be able to watch under the debugger
        registerType<int>();
        registerType<std::string>();
    }

    std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const
    {
        auto im = find(pi_any.type().name());
        if (im != end())
        {
            return im->second->castValue(pi_any);
        }
        else return std::make_unique<NonCastableValue>();
    }

private:
    template <typename T>
    void registerType()
    {
        this->insert(std::make_pair(typeid(T).name(),std::make_unique<AnyCaster<T>>()));
    }
};

//----------------------------------------------------------------------------------
std::unique_ptr<IAnyValue> displayStdAnyContent(const std::any& pi_any)
{   
    static const AnyCasterService s_service;
    return s_service.castValue(pi_any);
}

我在VS2017 v15.9.8下验证了这个提议

请记住,Natvis 只能根据您的需求在运行时评估方法(显式用户调用)。

std::any l_any;
l_any = 5;
l_any = std::string("it works!");

手表

于 2021-04-02T19:24:32.520 回答
-3

std::any 将取消输入您的数据。它必须被视为标准中 void* 的安全包装器。因此,您无法在运行时通过 natvis 查看其键入的数据。

我建议通过依赖模板类型的具体类继承的通用接口来重新设计您的实现以避免 std::any 或 void* 。例如:

class IData //base interface
{
public:
    ~virtual IData()=default;
}

template <typename T> 
class TypedData:public IData //abstract or concrete
{
    //...
    T m_data;
}

void genericFunction(const IData& pi_data); //instead of void* or std::any

int main()
{
    TypedData<std::string> l_data("my typed data");
    genericFunction(l_data);
    //...
}

通过这种设计,您可以定义单个 natvis 条目来管理 TypedData

<Type Name="TypedData&lt;*&gt;"> //TypedData<*>
    <DisplayString>{m_data}</DisplayString>
</Type>
于 2021-04-02T08:51:32.913 回答