C++ 如何实现避免未定义行为的侵入式链表?

C++ 如何实现避免未定义行为的侵入式链表?,c++,c++11,C++,C++11,这是几年来我第三次发现自己需要一个侵入式的链表,用于一个不允许boost的项目(询问管理层…) 这是我第三次发现我的侵入式链表实现工作得很好,但我真的不喜欢它使用未定义的行为——即当将指向列表节点的指针转换为指向包含该列表节点的对象的指针时 这段糟糕的代码目前看起来如下所示: struct IntrusiveListNode { IntrusiveListNode * next_; IntrusiveListNode * prev_; }; template <typen

这是几年来我第三次发现自己需要一个侵入式的链表,用于一个不允许boost的项目(询问管理层…)

这是我第三次发现我的侵入式链表实现工作得很好,但我真的不喜欢它使用未定义的行为——即当将指向列表节点的指针转换为指向包含该列表节点的对象的指针时

这段糟糕的代码目前看起来如下所示:

struct IntrusiveListNode {
    IntrusiveListNode * next_;
    IntrusiveListNode * prev_;
};

template <typename T, IntrusiveListNode T::*member>
class IntrusiveList {
// snip ...
private:
    T & nodeToItem_(IntrusiveListNode & node) {
        return *(T*)(((char*)&node)-((size_t)&(((T*)nullptr)->*member)));
    }

    IntrusiveListNode root_;
};
template <typename T>
class intrusive::node
{
    template <typename S, node<S> S::*> friend class intrusive::list;
    template <typename S, node<S> S::*> friend class intrusive::iterator;

    T*       next;
    node<T>* prev;
public:
    node(): next(), prev() {}
    node(node const&) {}
    void operator=(node const&) {}
};
class Node {
public:
    intrusive::node<Node> link0;
    intrusive::node<Node> link1;
    int                   n;
    Node(int n): n(n) {}
};
std::ostream& operator<< (std::ostream& out, Node const& n) {
    return out << n.n;
}

int main()
{
    intrusive::list<Node, &Node::link0> l0;
    intrusive::list<Node, &Node::link1> l1;

    Node n[] = { 10, 11, 12, 13, 14, 15 };

    l0.push_front(n[0]);
    l0.push_front(n[1]);
    l0.push_front(n[2]);

    l1.push_back(n[0]);
    l1.push_back(n[1]);
    l1.push_back(n[2]);

    std::cout << "l0=" << l0 << " l1=" << l1 << "\n";
}
struct IntrusiveListNode{
IntrusiveListNode*下一步;
IntrusiveListNode*上一个;
};
模板
阶级侵入论者{
//剪断。。。
私人:
T&nodeToItem_u2;(入侵列表节点和节点){
返回*(T*)((char*)和node)-(size_T)和((T*)nullptr)->*成员);
}
入侵节点根;
};
我真的不在乎
nodeToItem_uu
有多难看,但我想保持
IntrusiveList
的公共接口和语法不变。具体来说,我想使用
IntrusiveList
而不是
IntrusiveList
来指定列表类型的类型

快到2016年了——有没有办法不调用未定义的行为来实现这一点


编辑: 在评论中有一些建议的解决方案(涉及列表的不同结构),我想在这里总结一下:

  • 生活在未定义的行为中,因为语言似乎有任意的限制,阻止反向使用成员指针

  • IntrusiveListNode
    中存储指向包含类的附加指针。这可能是目前最干净的解决方案(无需更改接口),但确实需要在每个列表节点中使用第三个指针(可以进行小的优化)

  • IntrusiveListNode
    派生并使用
    static\u cast
    。在boost中,这是侵入式链表的
    base\u hook
    版本。我想坚持使用
    member\uhook
    版本,以避免引入多重继承

  • 存储指向下一个和上一个包含类的指针,而不是指向
    IntrusiveListNode
    中的下一个和上一个列表节点的指针。这使得在入侵列表中创建根节点变得困难。列表必须包含
    T
    的完整实例化(这是不可能的,例如,如果
    T
    是抽象的),或者列表的结尾需要是空指针(这将中断
    --list.end()
    ,只允许向前迭代)

  • Boost入侵列表有一个
    成员钩子
    版本,它可以以某种方式工作,但是它的实现还没有被理解(它可能还依赖于未定义的行为)


  • 问题仍然存在:是否有可能创建一个具有双向迭代支持、无未定义行为和无“不必要”内存开销的基于成员的侵入式列表?

    如果不调用UB,您很难用其中一个成员的指针获取原始对象。为什么你绝对不能?因为一个
    IntrusiveListNode
    可以放在任何地方。没有任何线索表明某个特定的
    IntrusiveListNode
    保存在
    T
    中,另一个证据是您不能这样做:编译器无法知道发送给函数的节点是否真的保存在
    T
    中。你试图做的是未定义的行为。正确的方法是在
    IntrusiveListNode
    中添加指向其容器的指针

    template<typename T>
    struct IntrusiveListNode {
        IntrusiveListNode * next_;
        IntrusiveListNode * prev_;
        T* item_;
    };
    
    template <typename T, IntrusiveListNode<T> T::*member>
    class IntrusiveList {
    // snip ...
    private:
        T & nodeToItem_(IntrusiveListNode<T> & node) {
            return *(node->item_);
        }
    
        IntrusiveListNode<T> root_;
    };
    
    模板
    结构IntrusiveListNode{
    IntrusiveListNode*下一步;
    IntrusiveListNode*上一个;
    T*第u3项;
    };
    模板
    阶级侵入论者{
    //剪断。。。
    私人:
    T&nodeToItem_u2;(入侵列表节点和节点){
    返回*(节点->项目);
    }
    入侵节点根;
    };
    
    如果不能将模板用于
    IntrusiveListNode
    ,则可以使用
    void*
    而不是
    t*


    您可以看到一个实施侵入式链表的示例

    我认为使用CRTP可以实现以下好处:

    #include <iostream>
    using namespace std;
    
    template<typename T>
    struct ListNode
    {
        ListNode<T>* next;
    
        // this would be nodeToItem in the list class
        T* value()
        {
            return static_cast<T*>(this);
        }
    };
    
    // This would be your abstract base class
    struct A: public ListNode<A>
    {
        A(int i): x(i) {}
        virtual ~A() = 0;
        int x;
    };
    
    inline A::~A() {}
    
    struct B: public A
    {
        B(int i): A(i) {}
        virtual ~B() {}
    };
    
    template<typename T>
    class IntrusiveList {
    public:
    IntrusiveList(ListNode<T>* ptr): root(ptr) 
    {
        ptr->next = nullptr;
    }
    
    void append(ListNode<T>* ptr)
    {
        ptr->next = root;
        root = ptr;
    }
    
    ListNode<T>* begin() {return root;}
    private:
    ListNode<T>* root;
    };
    
    int main() {
        B b(10);
        B b2(11);
        IntrusiveList<A> l(&b);
        l.append(&b2);
    
        for(ListNode<A>* n=l.begin(); n != nullptr; n = n->next)
        {
             std::cout << n->value()->x << std::endl;
        }
        return 0;
    }
    
    #包括
    使用名称空间std;
    模板
    结构列表节点
    {
    ListNode*下一步;
    //这将是list类中的nodeToItem
    T*值()
    {
    返回静态_cast(此);
    }
    };
    //这将是您的抽象基类
    结构A:公共ListNode
    {
    A(inti):x(i){}
    虚~A()=0;
    int x;
    };
    内联A::~A(){}
    结构B:公共A
    {
    B(inti):A(i){}
    虚拟~B(){}
    };
    模板
    阶级侵入论者{
    公众:
    IntrusiveList(ListNode*ptr):根(ptr)
    {
    ptr->next=nullptr;
    }
    无效附加(ListNode*ptr)
    {
    ptr->next=root;
    根=ptr;
    }
    ListNode*begin(){return root;}
    私人:
    ListNode*root;
    };
    int main(){
    B(10);
    B b2(11);
    侵入层l&b;
    l、 追加(&b2);
    对于(ListNode*n=l.begin();n!=nullptr;n=n->next)
    {
    
    std::cout value()->x如果您不介意更改
    IntrusiveListNode
    类型,您可以让一个节点包含指向上一个/下一个节点的句柄-您只需执行
    节点->句柄
    查找,而不必执行相反的操作。

    模板
    struct IntrusiveListHandle{
    Node*next=nullptr;
    //和节点*prev等。。。
    };
    模板
    结构侵入体{
    节点*第一;
    静态节点*下一个(节点*n){
    自动h=(n->*句柄)。下一步;
    }
    };
    
    用法示例:

    #include <iostream>
    
    struct Test {
        IntrusiveListHandle<Test> handle;
        std::string value;
    
        Test(const std::string &v): value(v) {}
    };
    
    template<typename IntrusiveList>
    void print(const IntrusiveList &list) {
        for (Test *n = list.first; n; n = list.next(n)) {
            std::cout << n->value << "\n";
        }
    }
    
    int main() {
        Test hello("hello");    
        Test world("world!");
        hello.handle.next = &world;
        IntrusiveList<Test, &Test::handle> list;
        list.first = &hello;
        print(list);
    }
    
    #包括
    结构测试{
    手柄;
    std::字符串值;
    测试(const std::string&v):值(v){}
    };
    模板
    无效打印(常量列表和列表){
    for(Test*n=list.first;n;n=list.next(n)){
    std::cout值
    问题仍然存在:是否有可能创建一个具有双向迭代支持、无未定义行为和无“不必要”内存开销的基于成员的入侵列表

    您尝试做的是取C++对象的非静态数据成员,并将其转换为

    struct intrusive_list_node
    {
      intrusive_list_node *next;
      intrusive_list_node *prev;
    
      template<typename T, size_t offset> T *convert()
      {
        auto p = reinterpret_cast<char*>(this); //Legal conversion, preserves address.
        p -= offset; //Legal offset, so long as `offset` is correct
        return reinterpret_cast<T*>(p); //`p` has the same value representation as `T*` did originally, so should be legal.
      }
    }
    
    #define CONVERT_FROM_MEMBER(node, T, member_name) node->convert<T, offsetof(T, member_name)>()
    
    template <typename T>
    class intrusive::node
    {
        template <typename S, node<S> S::*> friend class intrusive::list;
        template <typename S, node<S> S::*> friend class intrusive::iterator;
    
        T*       next;
        node<T>* prev;
    public:
        node(): next(), prev() {}
        node(node const&) {}
        void operator=(node const&) {}
    };
    
    template <typename T, intrusive::node<T> T::*Link>
    class intrusive::iterator
    {
        template <typename S, node<S> S::*> friend class intrusive::list;
        node<T>* current;
    
    public:
        explicit iterator(node<T>* current): current(current) {}
        T& operator*() { return *this->operator->(); }
        T* operator->() { return this->current->next; }
        bool operator== (iterator const& other) const {
            return this->current == other.current;
        }
        bool operator!= (iterator const& other) const {
            return !(*this == other);
        }
        iterator& operator++() {
            this->current = &(this->current->next->*Link);
            return *this;
        }
        iterator operator++(int) {
            iterator rc(*this);
            this->operator++();
            return rc;
        }
        iterator& operator--() {
            this->current = this->current->prev;
            return *this;
        }
        iterator operator--(int) {
            iterator rc(*this);
            this->operator--();
            return rc;
        }
    };
    
    template <typename T, intrusive::node<T> T::*Link>
    class intrusive::list
    {
        node<T> content;
    
    public:
        list() { this->content.prev = &this->content; }
        iterator<T, Link> begin() { return iterator<T, Link>(&this->content); }
        iterator<T, Link> end() { return iterator<T, Link>(this->content.prev); }
    
        T& front() { return *this->content.next; }
        T& back() { return *(this->content.prev->prev->next); }
        bool empty() const { return &this->content == this->content.prev; }
        void push_back(T& node) { this->insert(this->end(), node); }
        void push_front(T& node) { this->insert(this->begin(), node); }
        void insert(iterator<T, Link> pos, T& node) {
            (node.*Link).next = pos.current->next;
            ((node.*Link).next
             ? (pos.current->next->*Link).prev 
             : this->content.prev) = &(node.*Link);
            (node.*Link).prev = pos.current;
            pos.current->next = &node;
        }
        iterator<T, Link> erase(iterator<T, Link> it) {
            it.current->next = (it.current->next->*Link).next;
            (it.current->next
             ? (it.current->next->*Link).prev
             : this->content.prev) = it.current;
            return iterator<T, Link>(&(it.current->next->*Link));
        }
    };
    
    template <typename T, intrusive::node<T> T::*Link>
    std::ostream& intrusive::operator<< (std::ostream& out, intrusive::list<T, Link>& list)
    {
        out << "[";
        if (!list.empty()) {
            std::copy(list.begin(), --list.end(), std::ostream_iterator<T>(out, ", "));
            out << list.back();
        }
        return out << "]";
    }
    
    class Node {
    public:
        intrusive::node<Node> link0;
        intrusive::node<Node> link1;
        int                   n;
        Node(int n): n(n) {}
    };
    std::ostream& operator<< (std::ostream& out, Node const& n) {
        return out << n.n;
    }
    
    int main()
    {
        intrusive::list<Node, &Node::link0> l0;
        intrusive::list<Node, &Node::link1> l1;
    
        Node n[] = { 10, 11, 12, 13, 14, 15 };
    
        l0.push_front(n[0]);
        l0.push_front(n[1]);
        l0.push_front(n[2]);
    
        l1.push_back(n[0]);
        l1.push_back(n[1]);
        l1.push_back(n[2]);
    
        std::cout << "l0=" << l0 << " l1=" << l1 << "\n";
    }