【问题标题】:Template polymorphism and unique_ptr模板多态性和 unique_ptr
【发布时间】:2021-02-15 16:15:49
【问题描述】:

我目前正在尝试编写一个管道,该管道能够处理每个管道元素中的不同类型的数据。现在我想将unique_ptrs 与某种模板多态性和模板特化一起使用。

struct Start {}; // Dummy struct

template<typename In, typename Out>
class PipelineElement {
public:
    virtual Out process(In in) = 0;
};

// partial template specialization for the first pipeline element
template<typename Out>
class PipelineElement<Start, Out> {
public:
    virtual Out process() = 0;
};

class Producer : public PipelineElement<Start, int> {
    int process() override { ... }
};

现在一个函数应该采用一个部分专门化的PipelineElementunique_ptr。但是,以下内容不会编译并出现错误消息:

auto Pipeline::setStart(std::unique_ptr,std::default_delete>>)':无法从 'std::unique_ptr 转换参数 1 >' 到 'std::unique_ptr,std::default_delete>>

class Pipeline {
    template<typename Out>
    static auto setStart(std::unique_ptr<PipelineElement<Start, Out>> element) { ... }
};

int main() {
    Pipeline::setStart(std::make_unique<Producer>());
}

如果我改用常规指针,它编译时不会出现任何错误。

为什么普通指针的版本能编译,而智能指针的版本不能?

class Pipeline {
    template<typename Out>
    auto setStart(PipelineElement<Start, Out>* element) { ... }
};

int main() {
    Pipeline::setStart(new Producer());
}

【问题讨论】:

  • 在我的测试中(herehere),Pipeline::setStart(std::make_unique&lt;Producer&gt;()); 如果您明确指定模板参数而不是让编译器尝试推断它,则可以工作:Pipeline::setStart&lt;int&gt;(std::make_unique&lt;Producer&gt;());
  • 但请注意,通过PipelineElement&lt;Start, int&gt;* 指针删除Producer 对象将无法正常工作,因为PipelineElement 没有virtual 析构函数,因此Producer 析构函数不会不会被调用 (proof)。

标签: c++ templates polymorphism smart-pointers


【解决方案1】:

PipelineElement 没有虚拟析构函数。所以通过删除指向PipelineElement的指针来删除Producer会导致错误。

编辑:由于@RemyLebeau 信息。不幸的是,std::unique_ptr 无法解决这个问题。编译问题是因为编译器无法推导出setStart的模板参数。注意:请在未来提供真实的错误消息,而不是虚假消息。

推断模板参数的能力是有限的,除了模板参数搜索过程中的一些微不足道的转换外,不会尝试大多数形式的转换。如果您不想在每次建议接受更通用的输入类型并施加基于 SFINEA 的限制时都指定模板参数:

在这里,我写了一个基于限制输入unique_ptr&lt;T&gt;T 继承自PipelineElementBase 的示例。

#include <iostream>
#include <memory>
#include <type_traits>
using namespace std;
 
struct Start {}; // Dummy struct

class PipelineElementBase
{
    public:
    virtual ~PipelineElementBase() = default;
};

template<typename In, typename Out>
class PipelineElement : public PipelineElementBase
{
public:
    ~PipelineElement() { cout << "~PipelineElement<In,Out>" << endl; }
    virtual Out process(In in) = 0;
};
 
// partial template specialization for the first pipeline element
template<typename OutParam>
class PipelineElement<Start, OutParam> : public PipelineElementBase
{
public:
    using Out = OutParam;
    ~PipelineElement() { cout << "~PipelineElement<Start,Out>" << endl; }
    virtual Out process() = 0;
};
 
class Producer : public PipelineElement<Start, int>
{
public:
    ~Producer() { cout << "~Producer" << endl; }
    int process() override { return 1; }
};
 
class Pipeline
{
public:
    template<typename PE, std::enable_if_t<std::is_base_of_v<PipelineElementBase, PE>,int> = 0>
    static auto setStart(std::unique_ptr<PE> element) 
    { 
        using Out = typename PE::Out;
        return 1; 
    }
};
 
int main()
{
    Pipeline::setStart(std::make_unique<Producer>());
    return 0;
}

【讨论】:

  • "这两种类型的默认析构函数不能相互转换" - 我也这么认为,但this reference 建议std::default_delete&lt;Producer&gt; 应该转换为std::default_delete&lt;PipelineElement&lt;Start,int&gt;&gt;: "从另一个std::default_delete 对象构造一个std::default_delete&lt;T&gt; 对象。如果U* 可隐式转换为T*,则此构造函数将只参与重载决议 ... [this] 使从@987654337 的隐式转换成为可能@到std::unique_ptr&lt;Base&gt;"
  • @RemyLebeau 陌生人。本来我也想过推导模板参数失败,但是报错信息暗示模板参数被推导了……等OP的消息不是报错信息?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-26
  • 1970-01-01
  • 2021-12-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多