c++ - 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 上,所以不能使用变体或任何
有没有标准的方法来做到这一点。我想要的只是一个可以是多种类型(有界)的类的实例变量,并且在阅读它时检查它是什么类型
解决方案
简单的修复
解决您的问题的简单方法是向您添加一个模板函数is<T>()
,ParameterBase
该函数是根据dynamic_cast
指针定义的。dynamic_cast
with 指针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 )
这使用了 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 */
}
这是一个小的工作示例。
类型擦除解决方案 ( c++17 )
如果您正在寻找一组有限的实例化,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
推荐阅读
- webpack - 我无法安装本地 webpack-cli
- mysql - 在两个条件下获得第二高的值
- c++ - 重载结构运算符在乘以双精度时失去精度
- c# - 通过 PMC for mysql 添加 ef 核心迁移时如何修复错误?
- swift - 如何使用 UITableViewCell 中的按钮添加 TextView
- laravel - 来自移动浏览器的共享链接显示 ip 而不是域名
- react-native - 如何在 React Native 中显示渐变 SVG 图像
- android - Android - 电子邮件 InputField 不接受用户输入
- swift - 为什么这个排序算法会失败?
- xamarin - Xamarin 中的 Bitrise CI