【发布时间】:2020-11-16 23:08:14
【问题描述】:
我有一些代码接受一种类型的对象并根据第一种类型创建另一种类型的对象。 (类型之间存在 1->1 的关系。)我最初使用哈希表 (unordered_map<>) 和基于第一个对象类型的键来关联第二个对象的创建函数。但是当我更多地了解自上次全职 C++ 以来引入的 C++ 特性时,我发现了std::variant<>。
我已成功将实现转换为使用此 C++17 功能。但是,剩下的一块仍然有点麻烦。该设计在实例化第二个类的对象之前调用第二个类的静态成员函数以验证第一个对象的内容。为了立即处理这个问题,我使用了一个访问者结构,其中每个输入类型都重载了函数运算符。
我想知道是否有某种方法可以使用模板进行关联,而不是只复制类型不同的代码?
我已经尝试查看std::variant<> 的工作方式,并且我看到了可以使用.index() 获得类型索引的位置。我可以看到如何基于索引来实例化对象,如果我使用对象类型创建第二个std::variant<>,我可能会使用该索引。但是,如您所见,在验证参数之前,我不想实例化对象。执行此操作的函数是静态的,我看不到一种方法可以将 parms 类型与对象类型关联起来,从而让我可以进行静态调用。
(我也意识到这两个访问者结构可以在下面的代码中组合,但是在真实的代码中,创建更长更复杂,我宁愿在每个重载中都没有它的副本。)
struct Type1Parms {};
struct Type2Parms {};
struct Type3Parms {};
...
struct TypeBase {};
struct Type1 : public TypeBase
{
static bool ValidateParms(const Type1Parms&);
Type1(const Type1Parms&);
};
struct Type2 : public TypeBase
{
static bool ValidateParms(const Type2Parms&);
Type2(const Type2Parms&);
};
struct Type3 : public TypeBase
{
static bool ValidateParms(const Type3Parms&);
Type3(const Type3Parms&);
};
...
struct ValidateParmsVisitor
{
bool operator()(const Type1Parms& parms)
{
return Type1::ValidateParms(parms);
}
bool operator()(const Type2Parms& parms)
{
return Type2::ValidateParms(parms);
}
bool operator()(const Type3Parms& parms)
{
return Type3::ValidateParms(parms);
}
...
};
using TypeParms = std::variant<Type1Parms, Type2Parms, Type3Parms, ...>;
struct CreateObjectVisitor
{
std::unique_ptr<TypeBase> operator()(const Type1Parms& parms)
{
return std::make_unique<Type1>(parms);
}
std::unique_ptr<TypeBase> operator()(const Type2Parms& parms)
{
return std::make_unique<Type2>(parms);
}
std::unique_ptr<TypeBase> operator()(const Type3Parms& parms)
{
return std::make_unique<Type3>(parms);
}
...
};
template<typename TParms>
std::unique_ptr<TypeBase> CreateType(const TParms& parms)
{
unique_ptr<TypeBase> obj;
if (visit(ValidateParmsVisitor{}, parms))
obj = visit(CreateObjectVisitor{}, parms);
return std::move(obj);
}
有没有办法建立这种关联,尤其是作为可以与静态成员函数调用一起使用的类型?
编辑:我应该解释一下,这是一个更大的项目的一部分,还有许多其他设计标准塑造了它的设计。
例如,这是针对客户端接口的,其中 API 旨在尽可能简单地表达。客户端只有对 parms 结构的可见性(通过标头)和一个接受 parms 并返回包含上述对象的对象的函数。最初的设计确实有一个用于 parms 的基础结构,显然必须在公共标题中。但是,这意味着客户端可以从基类本身继承并将其传递给对象创建函数,或者从可接受的结构继承。为了避免段错误,这需要添加运行时检查以确保类型是可接受的,这主要由散列设计处理——尽管它不是那么简单。当我删除散列设计时,我也失去了这种类型验证的方法,但我意识到这将被使用variant<> 的编译时检查所取代,处理自定义结构(现在没有要检查的基础)。我还了解了处理继承问题的 final 关键字的 C++ 版本。
此外,虽然上面的代码没有显示,但 parms 结构包含多个成员,ValidateParms() 函数实际上尝试验证值和组合是否有效。
【问题讨论】:
-
I also realize that these two visitor structures can be combined in the code below, but in the real code, the creation is longer and more complicated, and I would rather not have copies of it in each overload.能否将验证移到创建访问者中,但将创建的返回值设为可选变量或将空值添加到创建的对象变量中? -
是的,但正如我提到的(以及你引用的),调用 validate 和 create 的函数有更多代码,包括两个调用之间的代码。这是一个简化的例子,让问题更清楚。
-
是的,在我看来,将通用创建代码移入函数或其他东西比做类型特征等更容易,但这取决于实际代码。
标签: c++ templates type-conversion variant visitor-pattern