嗨,StackOverflow社区!
我正在使用可变参数模板,继承和抽象工厂模式进行工作,现在正努力使其协同工作。看来我对这些主题的了解程度是目前为止最遥远的,因此,如果您能给我一个提示或代码示例,您将不胜感激!提前致谢 ;)
这是上下文(我很抱歉!有几行代码...):
我有一个基础课
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);
}
但是,我们可以添加更多:工厂的基类。(这些将与我的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
每个Derived类中每个方法中参数的数量或类型),因此它会迫使我创建compute(const std::any&)
和compute(const std::any&, const std::any&)
,等等。
(@MaxLanghof:谢谢!)一种(很丑陋的)解决方案是手动提供所有可能的计算重载作为虚拟函数。
是的,您的权利,我也觉得很奇怪(我不会说到“丑陋”,但我还没有更漂亮的解决方案,所以...),但它确实有效。在这里的缺点是,我将无法存储类P(及相关类)在自己的图书馆,因为我想在开始时单独关注(«MainProgram(主程序)»摆弄P期从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的构造函数,但是编译器无法再推断出参数类型...
谢谢大家的时间,帮助和建议!
您可以键入-擦除参数,只要您可以在呼叫站点指定它们即可。
最少:
#include <functional>
#include <any>
#include <map>
#include <iostream>
template<typename Ret>
struct AnyCallable
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
template<typename ... Args>
AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
Ret operator()(Args&& ... args)
{
return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...);
}
template<typename ... Args>
Ret compute(Args ... args)
{
return operator()(std::forward<Args>(args)...);
}
std::any m_any;
};
template<>
struct AnyCallable<void>
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
template<typename ... Args>
AnyCallable(std::function<void(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
void operator()(Args&& ... args)
{
std::invoke(std::any_cast<std::function<void(Args...)>>(m_any), std::forward<Args>(args)...);
}
template<typename ... Args>
void compute(Args ... args)
{
operator()(std::forward<Args>(args)...);
}
std::any m_any;
};
using P = AnyCallable<void>;
void A(std::string& str) {std::cout << "AP::compute i " << str << std::endl;}
void B(int i) {std::cout << "BP::compute i " << i << std::endl;}
class Thing
{
public:
Thing(){}
void compute()
{
int i = 100;
std::string str = "qwerty";
// additional computations...
ps["one"].compute<int>(i);
// additional computations...
ps["two"].compute<std::string&>(str);
}
private:
std::map<std::string, P> ps = { { "one", B }, { "two", A } };
};
非常感谢@Caleth!到目前为止,您的示例使我了解了很多事情。我用您的工作来尝试更好地挖掘,这导致了我帖子的第二次编辑(可悲)。再次感谢你的帮助 !