作为这个问题的一部分,我编写的示例代码可能看起来是人为的,没有多少有用的用途,但这是因为它是一个最小的例子,而不是一个复杂的代码,它不能简洁地表达问题。
我想逆转类型擦除的操作。我假设没有这样做的设计模式--如果有,我要么不知道它,要么还没有意识到它是如何被用于这个目的的。
考虑以下类型的擦除。
class Base
{
}
template<typename T>
class Typeless : Base
{
T data;
}这样做的目的是在容器中存储基类指针。
std::container<Base&> container;类型已被删除。
但是,在我的代码的其他地方,我想提供静态类型强制执行。
template<typename U>
class Reference
{
U &external_ref_to_object;
Reference(U &ref)
: external_ref_to_object(ref)
{}
}
Base *p_tmp = new Typeless<int>;
container.push_back(p_tmp);
Reference<int> r(p_tmp);这里的要点是,虽然由于使用了类型擦除模式,container可以包含任何类型的对象,但是Reference类应该强制使用正确的类型。
如果此示例令人困惑,则更多的上下文可能会有所帮助。我正在编写一些与数据库应用程序没有太大不同的东西。container基本上是要管理的所有数据的集合,不管数据是什么类型。(它避免为每个唯一的container<T>设置一个T,如下所示。)
// avoid this:
std::container<int> all_integer;
std::container<float> all_floating_point;
std::container<std::string> all_text;
std::container<void*> anything_else;因此类型擦除。
“再次获取类型”的目的是强制所有数据库列包含相同类型的对象。
DBColumn<int> my_column_contains_int_type_data;
my_column_contains_int_type_data.insert(42);PS:尽量不要因为这段代码显然没有编译而分心。它是用来演示这个问题的草图。
作为最后的评论,我突然意识到这与工厂的模式有些相似。(虽然不完全一样。)我们已经有目标了。我们不需要克隆它,只需要存储对它的引用。我们不需要从用户输入、网络、磁盘或其他外部源加载任何内容。所以它不是一个创意工厂,也不是一个克隆工厂。
工厂可以使用某种形式的集中管理和生成的唯一id来指示对象的类型。例如,可以从磁盘数据中保存和加载包含头中的标识符的数据,该标识符指示磁盘上的数据表示什么类型的对象,因此应该如何动态创建对象。
在我的例子中,每个派生类都有这样一个标识符(每个T在Typeless<T>中是唯一的),但是这似乎不是一个特别好的解决方案,因为我担心我可能会编写下面的代码块。
class DBColumn<T>
if(identifier == "INT")
{
if(typeof(T) is int)
{
// good
}
else
{
throw "BAD"; // bad
}
}
else if(...) // repeat for each of int, float, double, std::string, etc希望问题是很清楚的?
发布于 2022-04-22 22:21:22
您不需要单独的标识符,您可以简单地if (typeof所有可能的类型。
if(typeof(T) is Typeless<int>)
doIntColumnThing(static_cast<Typeless<int>>(T));
else if(typeof(T) is Typeless<float>)
doFloatColumnThing(static_cast<Typeless<float>>(T));
else if(typeof(T) is Typeless<double>)
doDoubleColumnThing(static_cast<Typeless<double>>(T));
else if(typeof(T) is Typeless<std::string>)
doDoubleColumnThing(static_cast<Typeless<std::string>>(T));(或者,您可以生成花哨的模板元记忆,使其美观,但从根本上讲也是如此)
更好的设计通常是将它们从一开始就分开:
DBColumn<Typeless<int>> my_column_1;
DBColumn<Typeless<std::string>> my_column_2;
std::vector<Base> all_cells;
void add_column_2(Typeless<std::string>& cell) {
my_column_2.add(cell);
all_cells.add(cell);
}最好的解决方法通常是将所有的操作都放在接口中,但我想您已经做了一个不可能的设计。我强烈建议重新设计它,以便您可以这样做,但我认为您不会。
class Base
{
virtual void doColumnThing();
}
template<typename T>
class Typeless : Base
{
T data;
void doColumThing() {
doColumnThing(data);
}
}发布于 2022-04-24 11:57:59
这里有一种模式,即命令模式。一个明确的目的是在可能需要“撤消”操作的情况下使用。其思想是封装执行和撤消操作所需的所有信息,使其不依赖于程序流中的特定点。
我知道这是一个Java页面,但它非常清楚地解释了这个概念:https://java-design-patterns.com/patterns/command/
要添加,撤消的GoF建议是在命令对象中存储可能由于操作而更改的任何原始状态信息。通过这种方式,命令对象知道如何在两个方向执行操作,并拥有将接收方返回到其原始状态所需的所有信息。因此,它与“调用者”类很好地解耦。
如果信息很复杂,您可以为该状态信息设置另一个对象,以防止在命令本身中阻塞。
在您的情况下,这很可能包括类型信息,或者在可能的情况下恢复正确的原始类型的其他方法。
发布于 2022-11-15 06:47:37
为什么不结合使用typeid和static_cast呢?std::any的实现就是这样做的。
#include <typeinfo>
#include <stdexcept>
#include <vector>
class Base
{
protected:
virtual ~Base(){}
public:
virtual std::type_info const& type_info() const = 0;
};
template<typename T>
struct Typeless : public Base
{
T data;
std::type_info const& type_info() const override {
return typeid(T);
}
};
template <typename T>
bool erases(Base const& typeless){
return typeless.type_info() == typeid(T);
}
template <typename T>
T cast(Base const& typeless){
if (!erases<T>(typeless)){
throw std::logic_error("Or some other kind of error handling");
}
return static_cast<Typeless<T> const&>(typeless).data;
}
template<typename U>
class Reference
{
U &external_ref_to_object;
public:
Reference(U &ref)
: external_ref_to_object(ref)
{}
};
int main()
{
std::vector<Base*> container;
Base *p_tmp = new Typeless<int>;
container.push_back(p_tmp);
int undone = cast<int>(*p_tmp);
Reference<int> r(undone);
}https://godbolt.org/z/EhYeTcsnx
如果您可以使用C++17,您可以只使用std::any,而不是推出您自己的类型擦除系统。
https://stackoverflow.com/questions/71975139
复制相似问题