C++ 如何实现避免未定义行为的侵入式链表?
这是几年来我第三次发现自己需要一个侵入式的链表,用于一个不允许boost的项目(询问管理层…) 这是我第三次发现我的侵入式链表实现工作得很好,但我真的不喜欢它使用未定义的行为——即当将指向列表节点的指针转换为指向包含该列表节点的对象的指针时 这段糟糕的代码目前看起来如下所示:C++ 如何实现避免未定义行为的侵入式链表?,c++,c++11,C++,C++11,这是几年来我第三次发现自己需要一个侵入式的链表,用于一个不允许boost的项目(询问管理层…) 这是我第三次发现我的侵入式链表实现工作得很好,但我真的不喜欢它使用未定义的行为——即当将指向列表节点的指针转换为指向包含该列表节点的对象的指针时 这段糟糕的代码目前看起来如下所示: struct IntrusiveListNode { IntrusiveListNode * next_; IntrusiveListNode * prev_; }; template <typen
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()
,只允许向前迭代)成员钩子
版本,它可以以某种方式工作,但是它的实现还没有被理解(它可能还依赖于未定义的行为)问题仍然存在:是否有可能创建一个具有双向迭代支持、无未定义行为和无“不必要”内存开销的基于成员的侵入式列表?如果不调用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";
}