如果我有一个类,并且它的数据类型可能是int
, float
, double
, char[]
, std::string
, std::vector
... 现在我使用 anenum
来指示数据是哪种类型,并使用 avoid*
来为数据动态分配内存。但是,我确信必须有一种更优雅的方式。如何在不使用的情况下实现它boost
?
2 回答
实现“Variant”或“Any”类型,正如其他人指出的那样,您已经可以使用一些实现。但是如果您不想使用 boost 或其他替代方案,您可以实现自己的简单版本。
您的类型需要 2 个结构,一个是您存储的基类,一个是保存实际对象的派生模板类。
让我们称它们为占位符和持有者:
这是基本结构:
/**
* @brief The place holder structure..
*/
struct PlaceHolder
{
/**
* @brief Finalizes and instance of the PlaceHolder class.
*/
virtual ~PlaceHolder() {}
/**
* @brief Gets the type of the underlying value.
*/
virtual const std::type_info& getType() const = 0;
/**
* @brief Clones the holder.
*/
virtual PlaceHolder * clone() const = 0;
};
这将是派生类:
template<typename ValueType>
struct Holder: public PlaceHolder
{
/**
* @brief Initializes a new instance of the Holder class.
*
* @param ValueType The value to be holded.
*/
Holder(const ValueType & value) : held(value) {}
/**
* @brief Gets the type of the underlying value.
*/
virtual const std::type_info & getType() const
{
return typeid(ValueType);
}
/**
* @brief Clones the holder.
*/
virtual PlaceHolder * clone() const
{
return new Holder(held);
}
ValueType held;
};
现在我们可以这样:
PlaceHolder* any = new Holder<int>(3);
我们可以像这样从中取回价值:
int number = static_cast<Holder<int> *>(any)->held;
这不是很实用,所以我们创建一个类来为我们处理所有这些东西,并向它添加一些商品,我们称之为 Any:
/**
* @brief This data type can be used to represent any other data type (for example, integer, floating-point,
* single- and double-precision, user defined types, etc.).
*
* While the use of not explicitly declared variants such as this is not recommended, they can be of use when the needed
* data type can only be known at runtime, when the data type is expected to vary, or when optional parameters
* and parameter arrays are desired.
*/
class Any
{
public:
/**
* @brief Initializes a new instance of the Any class.
*/
Any()
: m_content(0)
{
}
/**
* @brief Initializes a new instance of the Any class.
*
* @param value The value to be holded.
*/
template<typename ValueType>
Any(const ValueType & value)
: m_content(new Holder<ValueType>(value))
{
}
/**
* @brief Initializes a new instance of the Any class.
*
* @param other The Any object to copy.
*/
Any(const Any & other)
: m_content(other.m_content ? other.m_content->clone() : 0)
{
}
/**
* @brief Finalizes and instance of the Any class.
*/
virtual ~Any()
{
delete m_content;
}
/**
* @brief Exchange values of two objects.
*
* @param rhs The Any object to be swapped with.
*
* @return A reference to this.
*/
Any& swap(Any & rhs)
{
std::swap(m_content, rhs.m_content);
return *this;
}
/**
* @brief The assignment operator.
*
* @param rhs The value to be assigned.
*
* @return A reference to this.
*/
template<typename ValueType>
Any& operator=(const ValueType & rhs)
{
Any(rhs).swap(*this);
return *this;
}
/**
* @brief The assignment operator.
*
* @param rhs The value to be assigned.
*
* @return A reference to this.
*/
Any & operator=(const Any & rhs)
{
Any(rhs).swap(*this);
return *this;
}
/**
* @brief The () operator.
*
* @return The holded value.
*/
template<typename ValueType>
ValueType operator()() const
{
if (!m_content)
{
//TODO: throw
}
else if (getType() == typeid(ValueType))
{
return static_cast<Any::Holder<ValueType> *>(m_content)->held;
}
else
{
//TODO: throw
}
}
/**
* @brief Gets the underlying value.
*
* @return The holded value.
*/
template<typename ValueType>
ValueType get(void) const
{
if (!m_content)
{
//TODO: throw
}
else if (getType() == typeid(ValueType))
{
return static_cast<Any::Holder<ValueType> *>(m_content)->held;
}
else
{
//TODO: throw
}
}
/**
* @brief Tells whether the holder is empty or not.
*
* @return <tt>true</tt> if the holder is empty; otherwise <tt>false</tt>.
*/
bool isEmpty() const;
{
return !m_content;
}
/**
* @brief Gets the type of the underlying value.
*/
const std::type_info& getType() const;
{
return m_content ? m_content->getType() : typeid(void);
}
protected:
/**
* @brief The place holder structure..
*/
struct PlaceHolder
{
/**
* @brief Finalizes and instance of the PlaceHolder class.
*/
virtual ~PlaceHolder() {}
/**
* @brief Gets the type of the underlying value.
*/
virtual const std::type_info& getType() const = 0;
/**
* @brief Clones the holder.
*/
virtual PlaceHolder * clone() const = 0;
};
template<typename ValueType>
struct Holder: public PlaceHolder
{
/**
* @brief Initializes a new instance of the Holder class.
*
* @param ValueType The value to be holded.
*/
Holder(const ValueType & value) : held(value) {}
/**
* @brief Gets the type of the underlying value.
*/
virtual const std::type_info & getType() const
{
return typeid(ValueType);
}
/**
* @brief Clones the holder.
*/
virtual PlaceHolder * clone() const
{
return new Holder(held);
}
ValueType held;
};
protected:
PlaceHolder* m_content;
};
这个实现是基于Any of Ogre
例如,您可以像这样使用它:
int main()
{
Any three = 3;
int number = three.get<int>();
cout << number << "\n";
three = string("Three");
std::string word = three.get<string>();
cout << word << "\n";
return 0;
}
输出:
3
Three
如果存在有限的类型列表,请考虑访问者模式。它专为当您拥有一小组类型但希望对数据进行操作的许多算法时而设计的。您经常在 3d 图形场景图中看到它。它使您可以有效地将节点动态转换为任何类型,但只需要一对虚拟调用即可完成,而不是大量的动态转换。
class Visitor;
class IntNode;
class FloatNode;
class Node {
public:
virtual void accept(Visitor& inVisitor) = 0;
};
class Visitor {
public:
virtual void visit(IntNode& inNode) = 0;
virtual void visit(FloatNode& inNode) = 0;
};
class IntNode {
public:
virtual void accept(Visitor& inVisitor) { return inVisitor->visit(this); }
int& value() { return mValue; }
private:
int mValue;
}
class FloatNode {
public:
virtual void accept(Visitor& inVisitor) { return inVisitor->visit(this); }
float& value() { return mValue; }
private:
float mValue;
}
这个想法是您构建一个作为访问者的算法,并将该算法传递给节点。每个节点类型的接受函数“知道”节点的类型,因此它可以调用该特定类型的访问者函数。现在访问者知道了节点的类型,可以对其进行特殊处理。
例如,考虑复制一个节点,使用初始方式完成,然后使用新的访问者模式完成
OldNode* copyNodeOldWayWithEnums(OldNode* inNode)
{
switch(inNode->type) {
case INT_TYPE:
{
int* oldValue = static_cast<int*>(inNode->value);
OldNode* rval = new OldNode;
rval->type = INT_TYPE;
rval->value = new int(oldValue);
return rval;
}
case FLOAT_TYPE:
{
float* oldValue = static_cast<float*>(inNode->value);
OldNode* rval = new OldNode;
rval->type = FLOAT_TYPE;
rval->value = new float(oldValue);
return rval;
}
case:
throw std::runtime_error("Someone added a new type, but the copy algorithm didn't get updated");
}
}
class CopyVisitor
: public Visitor
{
public:
virtual visitor(IntNode& inNode) {
int value = inNode.value();
mResult = new IntNode(value);
}
virtual visitor(FloatNode& inNode) {
float value = inNode.value();
mResult = new FloatNode(value);
}
Node* mResult;
}
Node* copyNode(Node* inNode) {
CopyVisitor v;
inNode->accept(v);
return v.mResult;
}
访客模式的特征
- 它不是立即直观的。在出现在 Gang of Four 的设计模式(面向对象设计的权威书籍)中出现的设计模式中,它是最难理解的。是的,这是一个缺点......但它仍然值得
- 它是非常类型安全的。没有不可靠的 static_casts 或昂贵的 dynamic_casts
- 添加类型非常耗时,因此请确保在编写大量访问者之前了解节点类型。但是,如果您确实添加了一个类型,您会立即收到编译器错误,直到您的所有访问者都被更新。您使用的 enum 方法不会给您带来编译器错误——您必须等待运行时错误,这很难找到。
- 访问者模式在处理树结构方面非常有效。这就是 3d 场景图使用它的原因。如果你发现自己使用 std::vector<Node*> 之类的东西,你会发现这种模式非常有效
- Node 析构函数自然是虚拟的。这意味着您可以执行“删除 mNode”之类的操作,并让它安全地释放内存。您不必在析构函数中放置一个开关来找出 void* 背后的真实类型并正确删除它。
- 当节点类型较少且算法数量较多时效果最佳。
- 在一个地方(在访问者中)聚合算法代码做得很好,而不是在节点之间分布它。
现在,所有这些都假设您有一小部分节点类型。访客专为 5-20 种类型而设计。如果你想在你的节点结构中存储任何东西,boost::any 是一个很好的解决方案,你应该花时间安装 boost 并使用它。你不会打败它。