我希望有一个类根据我传递的字符串创建不同类型的对象。根据我的研究,这最好地描述了工厂设计模式。我正在成功实现它,但我遇到了一个设计问题:我不知道如何创建具有不同长度构造函数的对象。
让我们举一个名为Pet的抽象父类。从它有3个孩子:鱼,猫和狗。它们都继承了Pet的重量和颜色,所以它们都在它们的构造中。但是鱼可能想要一些鱼鳍和一个关于它是否是咸水鱼的布尔值。这是一个4参数构造函数。猫想要多少条腿。这是3个参数。对于5个参数,狗可能有腿,品种的参数,以及他是否与其他狗一起运作良好。
在C ++中,我知道没有任何反射,所以最常见的做法似乎只是声明一个字符串映射到函数指针,其中函数指针指向一个看起来像这样的函数:
template<typename T> Pet* createObject(int weight, std::string color) {return new T(weight, color);}
同样,我不确定如何在不影响其他对象调用的情况下将更多参数填充到调用中。构造
我可以想到两个解决方法:使新函数接受不同数量的参数,或者使构造函数的默认参数超过一定的大小。
根据我有多少不同的参数大小,解决方法1看起来有点过分。
解决方法2似乎忽略了构造函数的全部内容,因为在调用构造函数后我将被强制分配数据。
还有其他更好的解决方法吗?
答案 0 :(得分:2)
您可以使用可变参数模板并完美转发。
template<typename T, typename... Args>
Pet* createObject(Args&&... args) {
return new T(std::forward<Args>(args)...);
}
但是,由于任何指针都可以转换为其基类,因此如果此函数返回T*
可能会更好。此外,使用裸指针并不明智,因为您必须手动删除它们。最好使用shared_ptr
或unique_ptr
。对于这些类,已经有类似的工厂方法:make_shared
和make_unique
(后者仅在C ++ 14中)。或者,如果您的编译器不支持C ++ 11,那么您可以使用Boost中的shared_ptr
和make_shared
。
当然,当你在编译时知道你需要创建什么类型时,这个解决方案是有效的。如果你必须在运行时决定它,那么整个问题必须从不同的方向考虑,好像你不知道你要创建什么类型,那么你无法知道给它们的参数,除了所有类型的通用参数。在这种情况下,您需要的是abstract factory pattern。幸运的是,C ++(至少来自C ++ 11)提供了一种实现此模式的方法,而无需创建大量的类。例如,假设您必须创建从Pet
派生的某个类的实例。实际种类的宠物,它的大小和其他属性在其他地方决定,而宠物的名字在创建时决定。然后,你需要一个像这样的工厂:
typedef std::function<std::shared_ptr<Pet>(const std::string& name)> PetFactory;
在某些时候,您决定要创建一个Dog
(我将实际创建参数的含义留给您想象)。
PetFactory petFactory =
[](const std::string& name) {
return std::make_shared<Dog>(name, 12, "brown", 23.5);
}
当您真正创建它时,您只需要致电工厂:
std::shared_ptr<Pet> pet = petFactory("Pet Name");
答案 1 :(得分:0)
这是你需要的(原谅内存泄漏等)
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/40e550b1507cca31b0bb/raw/b83ceb0f8b4a2b6256f079f5887fc5243baedd4f/elapsed%2520time%25201.0.js"></script>
<div id="panel">
<div id="inputDiv">
<input id="update" type="button" value="update">
</div>
<div id="wrapAlpha">alpha:
<div id="alpha"></div>
</div>
<div id="fdg">
</div>
<div id="viz"></div>
我觉得有太多的演员,但这个想法应该是一个很好的起点。
答案 2 :(得分:0)
如果你创建一个对象,你已经知道它的参数,并且它将是一个Fish,你根本就不需要一个工厂:只需构建一个Fish就可以了。
您可以合理地使用工厂,以防您从调用者那里知道将从中产生哪个对象。例如,您可以为工厂方法输入一个字符串,也可以从文件中读取:工厂通过解析字符串来创建并返回正确的对象类型。
来电者不知道它是鱼还是狗:这是工厂方法的目的。
此外,当您可以通过继承添加更多“可构造”对象并覆盖虚拟创造者方法来扩展它时,可以使用工厂。如果方法具有不同的签名,则不会发生这种情况 - 它们实际上是不同的方法
答案 3 :(得分:0)
这很有趣!我已经使用了工厂子函数来解析流本身的参数。为了简化使用,我添加了注册商模式,当然还有很好的TMP剂量。
以下是删节代码:
/* Pet and derived classes omitted */
/* Registrar pattern omitted */
struct PetFactory {
using PetCtr = std::unique_ptr<Pet> (*)(std::istream &);
static auto make(std::istream &stream) {
std::string str;
stream >> str;
return ctrMap().at(str)(stream);
}
using PetCtrMap = std::map<std::string, PetCtr>;
static PetCtrMap &ctrMap();
};
template <class T, class... Args, std::size_t... Idx>
auto streamCtr_(std::istream &stream, std::index_sequence<Idx...> const &) {
std::tuple<Args...> args;
using unpack = int[];
unpack{0, (void(stream >> std::get<Idx>(args)), 0)...};
return std::make_unique<T>(std::move(std::get<Idx>(args))...);
}
template <class T, class... Args>
auto streamCtr(std::istream &stream) {
return std::unique_ptr<Pet>(streamCtr_<T, Args...>(
stream,
std::index_sequence_for<Args...>{}
));
}
int main() {
PetFactory::make("fish 1 silver 5 true");
PetFactory::make("cat 4 tabby 9");
PetFactory::make("dog 17 white husky playful");
}
<强>输出:强>
I'm a 1kg silver fish with 5 fins, living in salty water.
I'm a 4kg tabby cat with 9 lives.
I'm a 17kg white husky and I'm playful.
完整的评论代码可用here on Coliru。感谢您的挑战!
答案 4 :(得分:-1)
这个问题有很多种方法;我喜欢的那个可能不是最优雅的,但它非常明确。基本上,您创建了一个指向boost::any
指针的地图,现在所有构造函数都只是采用这样的地图。
using MyArgs = unordered_map<string, boost::any>;
class Fish {
Fish(MyArgs args) {
int num_fins = boost::any_cast<int>(args.at("num_fins"));
}
现在所有构造函数都具有相同的签名,因此您的工厂可以看起来像:
unique_ptr<Pet> factory(string animal_name, MyArgs args) {
auto func = factory_map.at(animal_name);
return func(args);
}
编辑:我还应该注意,如果你犯了一个错误,那就是你的MyArgs缺少一个参数或者有一个错误的类型,就会抛出异常。所以你可以得到一个很好的明确错误,甚至可以处理它,而不是获得UB。