【发布时间】:2020-01-31 12:03:30
【问题描述】:
你好 StackOverflow 社区!
我在工作中使用可变参数模板、继承和抽象工厂模式,现在正在努力使其协同工作。看来我已经达到了我目前对这些主题的了解的最远,所以如果你能给我一个提示或代码示例,你会得到我的全部感激!在此先感谢 ;)
这是上下文(我很抱歉!有几行代码......):
我有一个 Base 类
template<typename... Params>
class P
{
public:
virtual void compute(Params&... ps) = 0;
// other things ...
};
和派生的类
template<typename... Params>
class AP : public P<Params...>
{
public:
void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
private:
void _compute(std::string& str) {std::cout << "AP::compute str " << str << std::endl;}
};
using A = AP<std::string>;
template<typename... Params>
class BP : public P<Params...>
{
public:
void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
private:
void _compute(int& i) {std::cout << "BP::compute i " << i << std::endl;}
};
using B = BP<int>;
到这里,没问题!
如果我做一个小的main(),这没有任何问题:
int main()
{
std::unique_ptr<P<int>> p1 = std::make_unique<B>();
int i = 15;
p1->compute(i);
std::unique_ptr<P<std::string>> p2 = std::make_unique<A>();
std::string str = "abc";
p2->compute(str);
}
但是,我们可以添加更多:工厂的 Base 类。 (这些将与我的班级以外的其他班级一起使用P...如果您想知道为什么:))
template<typename Base>
class Creator
{
public:
virtual std::unique_ptr<Base> create() = 0;
};
template<class Key, class T>
class Factory
{
public:
void store(Key key, std::unique_ptr<Creator<T>>&& creator)
{
_crs[key] = std::move(creator);
}
std::unique_ptr<T> create(Key key)
{
return _crs[key]->create();
}
private:
std::map<Key, std::unique_ptr<Creator<T>>> _crs;
};
及其实现能够构建 P 相关的对象:
template<typename Derived, typename... Params>
class PCreator : public Creator<P<Params...>>
{
public:
std::unique_ptr<P<Params...>> create() override
{
return std::make_unique<Derived>();
}
};
template<typename... Params>
class PFactory : public Factory<std::string, P<Params...>>
{
public:
PFactory()
{
this->store("int", std::make_unique<PCreator<BP<int>>>);
this->store("string", std::make_unique<PCreator<AP<std::string>>>);
}
// create() and store() methods inherited
};
如果我实例化 PFactory,编译器显然无法完成它的工作,因为它需要 PFactory 的模板参数,这些参数会将它们转发给 Factory<std::string, P<Params...>>。
但是,我的工厂只能创建一种“类型”的 P 对象,即可以使用这些Params 的对象。
这就是我一个人走了多远(遗憾的是我的同事都没有能力帮助我......)
我的目标是能够写出这样的东西:
class Thing
{
const std::array<std::string, 2> a = {"one", "two"};
public:
Thing()
{
PFactory f;
for(const auto& e : a)
_ps[e] = std::move(f.create(e));
}
void compute()
{
int i = 100;
std::string str = "qwerty";
// additional computations...
_ps["one"]->compute(i);
// additional computations...
_ps["two"]->compute(str);
}
private:
std::map<std::string, std::unique_ptr<P>> _ps;
};
这是我尝试在 CompilerExplorer 上工作和返工的 PoC,以及上面的来源。
任何帮助将不胜感激!
[编辑]是的,我妄想我可以欺骗编译器以使用运行时信息创建各种方法签名。
解决方案总结:
(@walnut: 谢谢!) 让 compute 采用 std::any 或类似的东西
我不太了解std::any,但是在 rtfm-ing CppReference 之后,它可以完成这项工作,接受这样一个事实,即我需要将参数转换为我需要的参数类(并处理异常)。
可悲的是,在实际项目中,compute() 可以接受多个参数(我使用可变参数模板的原因......我不想关心每个 compute 方法中每个参数的数量或类型派生类),所以它会迫使我创建compute(const std::any&)和compute(const std::any&, const std::any&)等。
(@MaxLanghof:谢谢!)一种(丑陋的)解决方案是手动提供所有可能的计算过载作为虚函数。
是的,你说得对,我也觉得这很奇怪(我不会走到“丑陋”的地步,但我还没有更漂亮的解决方案,所以......),但它正在工作。 这里的缺点是我无法将类 P (和相关类)存储在它自己的库中,因为我一开始就想要分离关注点(«MainProgram» 玩 Ps 派生自 lib::P)。
(@MaxLanghof:谢谢!)在编译时完成整个 _ps 映射。
我还没有足够的 C++ 经验和知识来完成这样的事情。我需要解决这个问题,如果有人有特定的链接(我的意思是:不是 Google 上的第一个链接;))或示例,我会很高兴学习。
到目前为止,感谢您的回答!
[编辑]嗨!抱歉耽搁了,我刚刚回到这个项目,非常感谢您的想法和经验!这意味着很多!
我在 @Caleth 和 @KonstantinStupnik 的工作上做了一些工作(真的很感谢你的例子:他们帮助我理解了我在做什么!)并带着我的测试用例到达了这一点: https://gcc.godbolt.org/z/AJ8Lsm 我遇到了 std::bad_any_cast 异常,但不明白为什么......
我怀疑传递引用或我使用 lambda 将 compute 方法保存在 std::any 中的方式存在问题,但不能确定。
我尝试扩展在AnyCallable<void>::operator() 中接收的类型,以找到与 P 构造函数中存储函数的区别,但对我来说似乎相同。
我尝试将&AP::compute 传递给P的构造函数,但是编译器无法再推断出参数类型...
感谢大家的宝贵时间、帮助和建议!
【问题讨论】:
-
我不确定这一切的意义何在,但基本上:
_ps["one"]和_ps["two"]具有相同的静态类型。所以compute必须是接受不同类型的模板,在这种情况下它不能是virtual,或者它必须为它在基类中接受的不同类型重载。目前compute是基类中的一个非重载函数。那是行不通的。 -
我看到的让它以这种方式工作的唯一方法是让
compute采用std::any或类似的东西,但是调用者仍然必须知道@中对象的派生类型987654350@ apriori 为了不提供错误的参数类型,在这种情况下使用带有基类指针的容器是没有意义的。 -
编译器需要在编译时知道
(*_ps["..."])的类型。由于该类型是在编译时确定的,因此它不能依赖于运行时值。您希望该类型具有compute(i)和compute(str)方法。但是您不能拥有virtual和模板化的成员函数。一种(丑陋的)解决方案是手动提供所有可能的compute重载作为虚函数。你对_ps["one"]->compute(str);有什么期望? -
另一种可能的解决方案是在编译时进行整个
_ps映射。但是你不能(容易)使用运行时字符串值而不是例如"one"。通常,类型擦除要求 a) 整个接口是(非模板化)基类的一部分,或者 b) 调用者/用户代码知道他们需要检索什么类型。您目前两者都不满足,如果您想要一个异构容器,则必须选择其中一个选项。 -
template<typename... Params> class AP并没有真正起作用,只有AP<std::string>会起作用。
标签: c++ templates inheritance variadic-templates factory