我对元编程还很陌生,我正在尝试创建一个工厂的单例对象,该对象创建可克隆的对象。为此,我具有以下代码,这些代码也已在 live coliru
中共享我要实现的目标(并且它未包含在coliru链接的代码中)是限制工厂仅允许其typename T
到ICloneable
接口。
此ICloneable
接口定义如下:
template <typename T>
class ICloneable {
public:
virtual std::unique_ptr<T> clone() const = 0;
};
实例化工厂的代码如下所示。
我能看到的最接近的答案是factory of templated class。我当时认为类似类型特征的东西会有所帮助-例如std::is_same_v
-但我对这些元编程技术没有经验。
int main()
{
auto protoType = std::make_unique<Widget>(1,2);
const auto gpFactory = Factory<Widget>::getInstance();
gpFactory->registerType(std::move(protoType), 32u);
auto cloned = gpFactory->getClone(32u);
std::cout << *cloned;
return 0;
}
答案 0 :(得分:3)
第一个问题是gFactory
是const&
。 registerType
是非const
方法。
auto& gFactory = Factory<Widget>::getInstance();
解决此问题。
gFactory.registerType(protoType, 32u);
registerType
预期为unique_ptr<Widget>
。您正在传递unique_ptr<Widget>
,但是您正在尝试复制它。
您不能复制unique_ptr
。
gFactory.registerType(std::move(protoType), 32u);
接下来,这里缺少一个论点和类似的问题:
const auto& [iter, inserted] = mFactoryRegInfo.try_emplace(rkey, std::move(protoType));
并且您在main
中丢弃了nodiscard参数。
要求ICloneable<T>
实际上被视为通用代码中的反模式。
template<class T, class C=std::unique_ptr<T>>
struct can_clone:std::false_type{};
template<class T>
struct can_clone<T, decltype( std::declval<T const&>().clone() )>:std::true_type {};
template <typename T,
std::enable_if_t< can_clone<T>{}, bool > = true
>
class Factory final {
public:
//! Thread safe singleton pattern
static Factory& getInstance() {
static std::unique_ptr<Factory> pInstance = std::make_unique<Factory>(token{0});
return *pInstance;
}
//! Registers a new cloneable type in the factory.
[[nodiscard]] bool registerType(std::unique_ptr<T> protoType, const uint32_t rkey) {
// Critical Section
std::lock_guard<std::mutex> lock(MutexGuard);
const auto& [iter, inserted] = mFactoryRegInfo.try_emplace(rkey, std::move(protoType));
return inserted;
}
//! Factory function - returns newly cloned unique_ptr<T>.
[[nodiscard]] std::unique_ptr<T> getClone(const uint32_t rkey) const {
// Critical Section
std::lock_guard<std::mutex> lock(MutexGuard);
const auto& iter = mFactoryRegInfo.find(rkey);
if (iter != mFactoryRegInfo.end()) {
return iter->second->clone();
}
return nullptr;
}
//! C.67: A polymorphic class should suppress copying.
Factory(const Factory&) = delete;
Factory(Factory&&) noexcept = delete;
Factory& operator=(const Factory&) = delete;
Factory& operator=(Factory&&) noexcept = delete;
//! Defaulted destructor.
~Factory() = default;
private:
//! Singleton private constructor.
Factory() = default;
struct token { explicit token(int){} };
public:
explicit Factory(token):Factory() {}
private:
// UUID (uint32_t) to T mapping
std::map<uint32_t, std::unique_ptr<T>> mFactoryRegInfo{};
mutable std::mutex MutexGuard;
};
这仅需要T
有一个支持返回T::clone() const
的{{1}}方法。
一种改进是要求它返回可转换为 unique_ptr<T>
的类型。
还要注意,我清理了您的单例代码。注意,您不应将单例代码与功能代码混合使用;从单元测试到想要特定于文档的对象工厂,有很多原因在同一代码库中拥有多个unique_ptr<T>
。
如果需要,可以将Singleton作为模板元编程的单独一部分来实现。
当您意识到混合动态库加载时单身人士的生命变得异常复杂时,这将挽救您的生命。
答案 1 :(得分:3)
您可以在Factory
内使用这样的静态断言来确保T
实现ICloneable<T>
:
static_assert(std::is_convertible<T*, ICloneable<T>*>::value, "T must implement ICloneable<T>");
请注意,尽管存在std::is_base_of
特征,但std::is_convertible
确实是在这种情况下可以使用的正确类型特征。引用cppreference:
即使std::is_base_of<A, B>::value
是A
的私有,受保护或模棱两可的基类,
B
也是正确的。在许多情况下,std::is_convertible<B*, A*>
是更合适的测试。