【问题标题】:Creating a template constrained c++ factory [closed]创建模板约束的 C++ 工厂 [关闭]
【发布时间】:2019-06-12 19:04:51
【问题描述】:

我对元编程相当陌生,我正在尝试创建一个工厂单例来创建可克隆对象。为此,我在 live coliru

中分享了以下代码

我想要实现的事情(它不包含在 coliru 链接的代码中)是限制工厂只允许其 typename TICloneable 接口。

这个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;
}

【问题讨论】:

  • 对于第一个错误,我认为您需要在此处删除constconst auto&amp; gFactory = Factory&lt;Widget&gt;::getInstance(); 您正在将gFactory 修改为gFactory.registerType(protoType, 32u)
  • 请提供minimal reproducible example,并在问题中提供所有相关代码
  • 在发布问题之前,您应该真正修复所有不相关的编译错误(例如,尝试复制 unique_ptr 而不是移动它)。我本来打算写一个答案,但是有这么多不相关的错误,我什至无法解决您要解决的问题。
  • 谢谢,我刚刚修复了编译问题并重新设计了问题以专注于元编程方面

标签: c++ c++17 factory template-meta-programming


【解决方案1】:

第一个问题是gFactoryconst&amp;registerType 是非const 方法。

auto& gFactory = Factory<Widget>::getInstance();

解决这个问题。

gFactory.registerType(protoType, 32u);

registerType 期待 unique_ptr&lt;Widget&gt;。您正在传递 unique_ptr&lt;Widget&gt;,但您正在尝试复制它。

您不能复制unique_ptr

gFactory.registerType(std::move(protoType), 32u);

接下来,这里有一个缺少的论点和类似的问题:

  const auto& [iter, inserted] = mFactoryRegInfo.try_emplace(rkey, std::move(protoType));

然后你丢弃了main 中的 nodiscard 参数。

Live example.

要求ICloneable&lt;T&gt; 实际上被认为是泛型代码中的反模式。

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 有一个支持返回unique_ptr&lt;T&gt;T::clone() const 方法。

一个改进是要求它返回一个类型可转换为 unique_ptr&lt;T&gt;

另请注意,我清理了您的单例代码。请注意,您不应该将单例代码与功能代码混合在一起;从单元测试到需要特定于文档的对象工厂,有很多理由可以在同一代码库中拥有多个 Factory&lt;Bob&gt;

如果需要,可以将单例实现为模板元编程的一个单独位。

当您意识到混合动态库加载时单例生命周期变得异常复杂时,这将挽救您的生命。

Live example.

【讨论】:

  • 谢谢!我想我是在@drescherjm 的 const 提示之后在后台修复代码 - 这个问题已经过编辑,现在专注于元编程的真正问题
  • 这并没有回答最初的问题,如何将 Factory 模板参数限制为实现 ICloneable 的类型。
  • 我同意 - 这就是我刚刚评论的内容
  • @Pezo 该问题在我回答时已被编辑。
  • @Yakk-AdamNevraumont yikes - 你的第二个例子有点黑魔法。我跑了很多兔子洞试图理解它。我不确定整个令牌是什么(看起来像是对工厂实例使用唯一 ptr 的一种方式),我很好奇如何通过元编程实现单例,如您所指出的。
【解决方案2】:

您可以在Factory 中使用这样的静态断言来确保T 实现ICloneable&lt;T&gt;

static_assert(std::is_convertible<T*, ICloneable<T>*>::value, "T must implement ICloneable<T>");

Live example

请注意,尽管有 std::is_base_of 特征,std::is_convertible 确实是在这种情况下使用的正确类型特征。引用cppreference

std::is_base_of&lt;A, B&gt;::value 为真,即使 AB 的私有、受保护或不明确的基类。在许多情况下,std::is_convertible&lt;B*, A*&gt; 是更合适的测试。

【讨论】:

  • 谢谢,为什么'std::is_convertible'中的指针语义?它也适用于 'std::is_convertible>::value'
  • 因为你通过指针或引用来做多态性。没有指针对我不起作用...
  • 我的立场是正确的!删除指针时我错过了一个错误
  • 实际上,我认为 std::is_base_of 可能是一个更好的选择,因为它利用了“is a”关系,在这种情况下不需要指针技巧。 coliru.stacked-crooked.com/a/534fd9a282bf2740
  • 不,不是,我相应地扩展了我的答案(至少在你原来的问题的情况下)。为了检查您是否将正确的模板参数传递给ICloneable,我不太确定,在那里可能更合适。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-02
  • 1970-01-01
相关资源
最近更新 更多