我想确保由Thing::Factory
的不同实例创建的Thing
实例不能combine
d。
以下代码在运行时就是这样做的:
#include <cassert>
struct Thing {
struct Factory {
Thing make() {return Thing(this);}
};
static void combine(Thing t1, Thing t2) {
assert(t1.factory == t2.factory);
}
private:
Thing(Factory* factory_) : factory(factory_) {}
Factory* factory;
};
int main() {
Thing::Factory f1;
Thing t11 = f1.make();
Thing t12 = f1.make();
Thing::combine(t11, t12);
Thing::Factory f2;
Thing t21 = f2.make();
Thing t22 = f2.make();
Thing::combine(t21, t22);
Thing::combine(t11, t21); // Assertion failure
}
问题:在编译过程中有没有办法做到这一点?
我试过Thing
模板:
template<typename Tag>
struct Thing {
// Same code as before
};
并将客户端代码修改为:
struct Tag1;
struct Tag2;
int main() {
Thing<Tag1>::Factory f1;
Thing<Tag1> t11 = f1.make();
Thing<Tag1> t12 = f1.make();
Thing<Tag1>::combine(t11, t12);
Thing<Tag2>::Factory f2;
Thing<Tag2> t21 = f2.make();
Thing<Tag2> t22 = f2.make();
Thing<Tag2>::combine(t21, t22);
}
那就没有办法combine
t11
t21
了。 好的。
但仍然存在问题:
- 没有什么能阻止创建另一个
Thing<Tag1>::Factory
并combine
它make
Thing
t11
- 客户端必须手动声明标记类型
有没有一种模式也可以解决这些问题?
在回答之前,听起来您遇到了 X-Y 问题; 和 - 我怀疑你选择了正确的 Y。 考虑完全删除这些工厂,或者 - 仅将一个工厂用于具有公共基类的许多类。
为了在编译时实现这一点,工厂需要生成不同类型的值,或者combine()
必须能够在编译时运行。但是combine()
constexpr 对你没有帮助,因为工厂只在运行时生产输出......所以是的,类型差异化是您唯一的选择。我想某种标记很好 - 但是如果您可以先验地提供这些标签,为什么不从一开始就拥有不同的类型呢?就像你自己说的,目前还不清楚这些标签应该来自哪里。
无论如何,要禁止创建两个相同类型的工厂,您可以使用单例模式。无论如何,工厂在运行时工作,不妨让它在运行时创建。
- 没有什么禁止创建另一个 Thing::Factory 并将它制造的东西与 t11 结合起来
您可以有一个通用工厂基础,该基础在工厂构造函数中保留std::type_info
初始化的标记实例的静态std::set
。
如果该条目已在该std::set
中,则可以引发运行时异常
- 客户端必须手动声明标记类型
您还可以为工厂实例生成 ID,而不是使用模板参数。