【问题标题】:How to know a generic type T if it has an appropriate constructor for use?如果泛型类型 T 有合适的构造函数供使用,如何知道它?
【发布时间】:2022-01-07 15:13:43
【问题描述】:

我正在实现一个双向链表,它以哨兵节点为头和尾,比如这个名为List 的类。 NodeList 中的私有结构。该类有一个私有方法Init用于初始化头尾节点,在List的构造函数中调用。

template<typename T>
class List {
public:
    List() {
        Init();
    }
    ...

private:
    struct Node {
        T data;
        Node* prev;
        Node* next;
        
        // Constructors
    };

    size_t size;
    Node* head;
    Node* tail;

    void Init() {
        // Codes arise some problem if instances of T have no default constructor.
        head = new Node;
        tail = new Node;
        head->prev = nullptr;
        head->next = tail;
        tail->prev = head;
        tail->next = nullptr;
        size = 0;
    }
};

现在的问题是,如果T 的实例没有默认构造函数,我无法使用head = new Node;tail = new Node; 创建哨兵节点。 new 运算符总是分配一块内存并构造它。在构造Node对象时,必须使用T的一些构造函数来初始化Node中的data字段。

有没有办法检查T 的哪些构造函数(除了复制和移动构造函数)我可以用来构造T 类型的变量data?或者我可以只初始化Node 中的prevnext 字段,而使data 字段未初始化?

【问题讨论】:

  • 如果T 没有默认构造函数,那么你会得到一个编译时错误。
  • 即使你知道它有什么构造函数,这对你有什么帮助?假设您发现有 T(X, int, int, bool, Y, Z) 构造函数。你会用这些信息做什么?它实际上是没有用的。请记住,您处于通用上下文中。正确的做法是:永远不要初始化 T 对象,除非您的 api 用户明确请求它。你不知道T 是什么。也许即使它有一个默认构造函数,它也是一个非常昂贵的操作。
  • Init() 是否打算创建一个包含0 元素的List?如果是这种情况,您可能希望 Init() 的设计也创建零 Ts。
  • struct Node { virtual ~Node() = default; Node* prev; Node* next; }; struct DataNode : Node { T data; };
  • 你不需要为headtail动态分配节点来指向,它们和拥有的List具有相同的生命周期

标签: c++ algorithm data-structures linked-list doubly-linked-list


【解决方案1】:

您的代码有一个类型 T 应该遵循您需要的概念。

在您的情况下,您希望它是默认可构造的。如果模板的实例化器没有提供 T 的符合,它会得到一个很大的模板错误。

您可以在代码中添加一个 static_assert 以提供更好的消息(如果您的方法没有直接使用,请提前提供)

您可以使用:static_assert(is_default_constructible_v)。 存在更多变体,如果它不应该抛出...,请参阅documentation

在 C++20 中,你也可以使用概念来限制 T。我对它们不太熟悉,所以我会留给你找到正确的语法。

还有其他解决方案,例如存储 std::optional,或在 init 函数上使用可变参数来构造 T。 在这种情况下,您可能希望使用std::is_constructible 限制代码。如果您不介意安置新闻,std::aligned_storage 也可以使用。一切都取决于你想用这个实现什么。

【讨论】:

  • 在我的原始代码head = new Node;中,我的目标并不是真正使用它的默认构造函数或其他构造函数来构造T,而是我只需要成功构造这样一个T,否则我不能使用哨点节点的想法。我不知道我可以在T 中使用什么构造函数来完成这个构造。说class F {public: F(const F&amp; x) {} F(F&amp;&amp; x) {}};,这个类是病态的,但是std::list&lt;F&gt; list;是完全合法的。我也想在我的代码中实现这一点。重构struct Node是一个解决方案,如果有其他解决方案,idk。
  • 在这种情况下,您需要 optional 或 aligned_storage
  • 谢谢,我明天试试这个解决方案。
  • 听起来不错,为简单起见,我建议您使用 std::optional
  • 是的,将T data改成std::optional&lt;T&gt; data后,也解决了我的问题,只是需要C++ 17编译器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-24
  • 2017-03-12
相关资源
最近更新 更多