我正在使用C ++模板编写一个抽象工厂,并遇到了一个小障碍。即,泛型类T可以提供以下一种或多种方式来构造对象:
static T* T::create(int arg);
T(int arg);
T();
我正在编写抽象工厂类,以便它可以按给定的顺序自动尝试这三种潜在的构造:
template <class T>
class Factory {
public:
T* create(int arg) {
return T::create(arg); // first preference
return new T(arg); // this if above does not exist
return new T; // this if above does not exist
// compiler error if none of the three is provided by class T
}
};
如何使用C ++模板实现此目的?谢谢。
答案 0 :(得分:5)
沿着这条线的东西应该有效:
struct S { static auto create(int) { return new S; } };
struct T { T(int) {} };
struct U {};
template<int N> struct tag: tag<N-1> {};
template<> struct tag<0> {};
class Factory {
template<typename C>
auto create(tag<2>, int N) -> decltype(C::create(N)) {
return C::create(N);
}
template<typename C>
auto create(tag<1>, int N) -> decltype(new C{N}) {
return new C{N};
}
template<typename C>
auto create(tag<0>, ...) {
return new C{};
}
public:
template<typename C>
auto create(int N) {
return create<C>(tag<2>{}, N);
}
};
int main() {
Factory factory;
factory.create<S>(0);
factory.create<T>(0);
factory.create<U>(0);
}
它基于sfinae和标签调度技术
基本思想是将工厂的create
函数转发给一组内部函数。由于存在tag
,这些函数会按顺序测试您要查找的功能,如果测试失败则会丢弃。由于sfinae,只要其中一个成功,代码就会编译,一切都按预期工作。
这是C ++ 17中的类似解决方案:
#include <type_traits>
#include <iostream>
#include <utility>
struct S { static auto create(int) { return new S; } };
struct T { T(int) {} };
struct U {};
template<typename C> constexpr auto has_create(int) -> decltype(C::create(std::declval<int>()), bool{}) { return true; }
template<typename C> constexpr auto has_create(char) { return false; }
struct Factory {
template<typename C>
auto create(int N) {
if constexpr(has_create<C>(0)) {
std::cout << "has create" << std::endl;
return C::create(N);
} else if constexpr(std::is_constructible_v<C, int>) {
std::cout << "has proper constructor" << std::endl;
return new C{N};
} else {
std::cout << "well, do it and shut up" << std::endl;
(void)N;
return C{};
}
}
};
int main() {
Factory factory;
factory.create<S>(0);
factory.create<T>(0);
factory.create<U>(0);
}
感谢@StoryTeller和@ Jarod42在这个艰难的早晨提供帮助 在wandbox上查看并运行。
答案 1 :(得分:0)
好的,感谢@skypjack的the answer我能够提出一个更兼容的解决方案,适用于pre c ++ 11编译器。核心思想是相同的,即使用标签调度进行有序测试。我没有依赖于decltype,而是使用了sizeof
和SFINAE的虚拟类。
struct S { static auto create(int) { return new S; } };
struct T { T(int) {} };
struct U {};
template<class C, int=sizeof(C::create(0))> struct test_1 { typedef int type; };
template<class C, int=sizeof(C(0))> struct test_2 { typedef int type; };
template<class C, int=sizeof(C())> struct test_3 { typedef int type; };
template<int N> struct priority: priority<N-1> {};
template<> struct priority<0> {};
class Factory {
template<typename C>
C* create(priority<2>, typename test_1<C>::type N) {
return C::create(N);
}
template<typename C>
C* create(priority<1>, typename test_2<C>::type N) {
return new C(N);
}
template<typename C>
C* create(priority<0>, typename test_3<C>::type N) {
return new C();
}
public:
template<typename C>
C* create(int N) {
return create<C>(priority<2>(), N);
}
};
int main() {
Factory factory;
factory.create<S>(0);
factory.create<T>(0);
factory.create<U>(0);
}
不确定是否可以将(失败)稍微丑陋的部分是使用sizeof
部分填充到私有函数签名中;如果是这样,我们也可以摆脱虚拟类。0
运算符的常量(在这种情况下为sizeof
),这可能会得到如果构造函数采用非常复杂类型的参数,那就太棘手了。