首页 > 解决方案 > C++ 检查模板实例变量的类型

问题描述

我有类似于这个问题的用例

我想检查参数中存储了哪种类型的实例变量而不抛出异常

class ParameterBase
{
public:
    virtual ~ParameterBase() {}
    template<class T> const T& get() const; //to be implimented after Parameter
    template<class T, class U> void setValue(const U& rhs); //to be implimented after Parameter
};

template <typename T>
class Parameter : public ParameterBase
{
public:
    Parameter(const T& rhs) :value(rhs) {}
    const T& get() const {return value;}
    void setValue(const T& rhs) {value=rhs;}    
private:
    T value;
};

//Here's the trick: dynamic_cast rather than virtual
template<class T> const T& ParameterBase::get() const
{ return dynamic_cast<const Parameter<T>&>(*this).get(); }
template<class T, class U> void ParameterBase::setValue(const U& rhs)
{ return dynamic_cast<Parameter<T>&>(*this).setValue(rhs); }

class Diagram
{
public:
    ParameterBase* v;
    int type;
};

我想要做的是这样的


if (diagram.getParameter().type == int) {
}


如何更改此实现,以便我可以查看所持有的 Parameter 类型

谢谢大家的回答,再多加分

我在 C++ 11 上,所以不能使用变体或任何

有没有标准的方法来做到这一点。我想要的只是一个可以是多种类型(有界)的类的实例变量,并且在阅读它时检查它是什么类型

标签: c++

解决方案


简单的修复

解决您的问题的简单方法是向您添加一个模板函数is<T>()ParameterBase该函数是根据dynamic_cast指针定义的。dynamic_castwith 指针nullptr在失败时返回,不像引用会抛出std::bad_cast. 例如:

class ParameterBase
{
public:
    ...
    template <typename T>
    bool is() const;
};

...

template <typename T> 
bool ParameterBase::is() const
{ 
    return dynamic_cast<const Parameter<T>*>(this) != nullptr;
}

用途很简单:

if (diagram.getParameter().is<int>()) {
    ...
}

但是请注意,整个设计并不是特别好。它以高度耦合的方式在基础和派生之间具有循环依赖关系。此外,它需要ParameterBase作为指针存在才能正确操作;价值语义会更加连贯(如果可能的话)

如果您可以使用类型擦除会更好,即使您Parameter根据它进行定义(这就是 C++17std::any将为您做的)。您链接问题中的第二个答案已经描述了这可能是什么样子。


类型擦除解决方案 ( )

这使用了 C++11 功能,如转发引用、右值引用和unique_ptr-- 但该概念也可以应用于早期的 C++ 版本。

对于类型擦除,您需要一个至少包含以下两个功能的接口:

  • 获取对模板化类型的引用,以及
  • 获取当前类型的标识符。

由于 C++ 中的接口不能是virtual,我们必须创造性地返回引用。C++ 有void*which 可以是任何类型的指针。如果误用(例如在错误类型之间进行转换),这可能会很糟糕;但是如果我们知道底层的类型,就可以完美了。值得庆幸的是,我们知道底层类型。

可以通过以下方式实现快速的类型擦除:

#include <type_traits> // std::decay
#include <utility>     // std::forward
#include <typeinfo>    // std::type_info, std::bad_cast
#include <memory>      // std::unique_ptr

class Parameter
{
private:

   // This is the interface we will implement in all instances
   struct Interface {
       virtual ~Interface() = default;
       virtual void* get() = 0;
       virtual const std::type_info& type() const = 0;
   };

   // This is the concrete instantiation of the above interfaces
   template <typename T>
   struct Concrete : public Interface {
       template <typename U>
       Concrete(U&& u) : m_value{std::forward<U>(u)} {}

       void* get() { return &m_value; }
       const std::type_info& type() const { return typeid(T); }

       T m_value; // actually holds the value here
   };

   // This holds onto the interface, and only the interface
   std::unique_ptr<Interface> m_interface;

public:

   // Constructs a parameter and sets the first interface value
   template <typename T>
   explicit Parameter(T&& value)
       : m_interface{new Concrete<typename std::decay<T>::type>{std::forward<T>(value)}}
   {}
   Parameter(Parameter&&) = default;
   Parameter& operator=(Parameter&&) = default;

   // Check if we are the same type by comparing the typeid
   template <typename T>
   bool is() const {
       return typeid(T) == m_interface->type();
   }

   // Get the underlying value. Always check that we are the correct type first!
   template <typename T>
   const T& get() const {
       // do the error handling ourselves
       if (!is<T>()) { throw std::bad_cast{}; }

       // cast void* to the underlying T*. We know this is safe
       // because of our check above first
       return (*static_cast<T*>(m_interface->get()));
   }

   // Set the underlying value. Always check that we are the correct type first!
   template <typename T, typename U>
   void set(U&& value) {
       // do the error handling ourselves
       if (!is<T>()) { throw std::bad_cast{}; }

       (*static_cast<T*>(m_interface->get())) = std::forward<U>(value);
   }

};

在上面,我们自己承担了检测底层类型的任务——但我们移除了循环耦合。我们现在还有一个合适的值类型,我们可以像普通变量一样移动,这真的很有帮助,因为它允许我们从 API 返回这个对象,而不用担心生命周期或所有权。

如果还需要可复制性,则可以扩展接口以具有clone()返回副本的功能或其他东西

使用这个对象,代码变成:

if (parameter.is<int>()) {
    /* treat parameter as an int */
} 

这是一个小的工作示例


类型擦除解决方案 ( )

如果您正在寻找一组有限的实例化,std::variant则可以用于此目的。如果可能的基础类型的数量是无限的,您应该查看std::any

在任何一种情况下,这里使用层次结构都是肤浅的(至少在当前示例中),因为整个类型擦除可以简化为具有查询包含能力的单一类型。这可以很容易地完成,std::any例如:

#include <any> // std::any, std::any_cast

class Parameter
{
public:

    // This implementation changes the active type if 'T' is not the same as the stored
    // value. If you want to restrict this, you can do error checking here instead.
    template <typename T>
    void set(const T& value) { m_value = value; }

    template <typename T>
    const T& get() { return std::any_cast<const T&>(m_value); }

    template <typename T>
    bool is() const noexcept { return m_value.type() == typeid(T); }

private:

    std::any m_value;
};

如果您不希望活动成员更改,可以通过is<T>()首先检查并以某种方式处理错误来限制。

查询活动类型可以简单地通过执行以下操作来实现:

if (parameter.is<int>()) {
    /* treat parameter as an int */
} 

如果类型是固定的,你总是可以使用std::variant代替 usingstd::has_alternative的定义is


推荐阅读