我有以下代码,现在称为C++98
它实现了一个双向链表,我正在考虑将其更新为C++11
,但我有以下问题:
std::unique_ptr
是有道理的,但在那里
仍需要其他指针“共享”unique_ptr。如果
nxt
是unique_ptr,我该如何处理prv
和iterator::it
?T
类型的分配器,而不是LLNode? li>
move
语义,应该定义哪些方法,例如
operator=() &&
等等?一个公认的答案会简要地讨论这些问题,然后在C++11
重新实现(诚然,这是很多代码,但大多数都会被剪切和粘贴)
一个好的答案将简要讨论这些问题,然后重新实现C++11
最小数量,以说明如何应用这些概念
使用标准的algorythms是有道理的,使用标准容器会破坏目的
鼓励在有意义的地方使用C ++ 11功能。 foreach
而不是仅使用lambdas
,因为它可以正常工作
这不是家庭作业
代码:
template <class T>
class LinkedList
{
class LLNode
{
public:
LLNode() // For Sentinel (requires that T has a default constructor)
{} // set prv/nxt directly
explicit LLNode(const T& x) : data(x) // For normal nodes
{} // set prv/nxt directly
T& get_data() const
{
return const_cast<T&>(data);
}
LLNode * prv;
LLNode * nxt;
private:
T data;
};
public:
class iterator
{
public:
iterator(const LinkedList * p, LLNode * i)
: parent(const_cast<LinkedList *>(p)), it(i)
{}
iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
iterator operator ++(int) // post
{
iterator ret=*this;
it = it->nxt;
return ret;
}
iterator& operator --() // pre
{
it = it->prv;
return *this;
}
iterator operator --(int) //post
{
iterator ret=*this;
it = it->prv;
return ret;
}
T& operator *() const
{
return it->get_data();
}
T * operator ->() const
{
return &(it->get_data());
}
bool operator ==(const iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const iterator& rhs) const
{
return it != rhs.it;
}
void erase()
{
parent->remove(it);
}
void insert_after(const T& x)
{
LLNode * add= new LLNode(x);
parent->insert(it, add);
}
void insert_before(const T& x)
{
LLNode * add= new LLNode(x);
parent->insert(it->prv, add);
}
private:
LinkedList * parent;
LLNode * it;
};
// Linked List class definition
LinkedList()
{
init();
}
LinkedList(const LinkedList& rhs)
{
init();
cp(rhs);
}
~LinkedList()
{
rm();
}
LinkedList operator =(const LinkedList& rhs)
{
if (this != &rhs)
{
rm();
cp(rhs);
}
return *this;
}
iterator begin() const
{
return iterator(this, sentinel.nxt);
}
iterator rbegin() const
{
return iterator(this, sentinel.prv);
}
iterator end() const
{
return iterator(this, const_cast<LLNode *>(&sentinel));
}
T& get_first() const // illegal if is_empty() is true
{
return sentinel.nxt->get_data();
}
T& get_last() const // illegal if is_empty() is true
{
return sentinel.prv->get_data();
}
size_t size() const
{
return count;
}
bool is_empty() const
{
return count==0;
}
void insert_first(const T& x)
{
LLNode * add= new LLNode(x);
insert(&sentinel, add);
}
void insert_last(const T& x)
{
LLNode * add= new LLNode(x);
insert(sentinel.prv, add);
}
void erase_first() // illegal if is_empty() is true
{
remove(sentinel.nxt);
}
void erase_last() // illegal if is_empty() is true
{
remove(sentinel.prv);
}
private:
void insert(LLNode * before, LLNode * added)
{
LLNode * after=before->nxt;
added->prv=before;
added->nxt=after;
before->nxt=added;
after->prv=added;
++count;
}
void remove(LLNode * node) // illegal if is_empty() is true
{
node->prv->nxt=node->nxt;
node->nxt->prv=node->prv;
delete node;
--count;
}
void cp(const LinkedList& rhs)
{
for (iterator i=rhs.begin(); i != rhs.end(); ++i)
{
insert_last(*i);
}
}
void rm()
{
LLNode * run=sentinel.nxt;
while (run != &sentinel)
{
LLNode * dead=run;
run=run->nxt;
delete dead;
}
}
void init()
{
count=0;
sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
}
LLNode sentinel;
size_t count;
};
编辑 - 基于Mooing Duck's answer的C ++ 11尝试:
template <class T, class ALLOC=std::allocator<T> >
class LinkedList
{
struct LLNode
{
LLNode * prv;
LLNode * nxt;
T& get_data() { return data; }
T data;
};
public:
class iterator
{
public:
using difference_type = ptrdiff_t;
using value_type = T;
using reference = T&;
using pointer = T*;
using iterator_category = std::bidirectional_iterator_tag;
iterator(LinkedList * p, LLNode * i) : parent(p), it(i)
{}
iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
iterator operator ++(int) // post
{
iterator ret=*this;
it = it->nxt;
return ret;
}
iterator& operator --() // pre
{
it = it->prv;
return *this;
}
iterator operator --(int) //post
{
iterator ret=*this;
it = it->prv;
return ret;
}
const T& operator *() const
{
return it->get_data();
}
T& operator *()
{
return it->get_data();
}
const T * operator ->() const
{
return &(it->get_data());
}
T * operator ->()
{
return &(it->get_data());
}
bool operator ==(const iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const iterator& rhs) const
{
return it != rhs.it;
}
void erase()
{
parent->remove(it);
}
void insert_after(T& x)
{
auto add=parent->alloc_node(x);
parent->insert(it->nxt, add);
}
void insert_before(T& x)
{
auto add=parent->alloc_node(x);
parent->insert(it, add);
}
private:
LinkedList * parent;
LLNode * it;
};
class const_iterator
{
public:
using difference_type = ptrdiff_t;
using value_type = const T;
using reference = const T&;
using pointer = const T*;
using iterator_category = std::bidirectional_iterator_tag;
const_iterator(const LinkedList * p, const LLNode * i) : parent(p),
it(const_cast<LLNode *>(i))
{}
const_iterator(iterator& cvt) : parent(cvt.parent), it(cvt.it)
{}
const_iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
const_iterator operator ++(int) // post
{
const_iterator ret=*this;
it = it->nxt;
return ret;
}
const_iterator& operator --() // pre
{
it = it->prv;
return *this;
}
const_iterator operator --(int) //post
{
const_iterator ret=*this;
it = it->prv;
return ret;
}
const T& operator *() const
{
return it->get_data();
}
const T * operator ->() const
{
return &(it->get_data());
}
bool operator ==(const const_iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const const_iterator& rhs) const
{
return it != rhs.it;
}
private:
const LinkedList * parent;
LLNode * it;
};
using my_alloc=typename
std::allocator_traits<ALLOC>::template rebind_alloc<LLNode>;
using my_traits=typename
std::allocator_traits<ALLOC>::template rebind_traits<LLNode>;
// Linked List class definition
LinkedList(const ALLOC& alloc = ALLOC() ) : mem(alloc)
{
init();
}
LinkedList(const LinkedList& rhs) : mem(rhs.mem)
{
init();
cp(rhs);
}
LinkedList(LinkedList&& rhs) : mem(rhs.mem) // Move
{
init();
shallow_cp(rhs);
}
~LinkedList()
{
rm();
}
LinkedList operator =(const LinkedList& rhs)
{
if (this != &rhs)
{
rm();
cp(rhs);
}
return *this;
}
LinkedList operator =(LinkedList&& rhs) // Move
{
if (this != &rhs)
{
rm();
shallow_cp(rhs);
}
return *this;
}
const_iterator begin() const
{
return const_iterator(this, sentinel.nxt);
}
iterator begin()
{
return iterator(this, sentinel.nxt);
}
const_iterator rbegin() const
{
return const_iterator(this, sentinel.prv);
}
iterator rbegin()
{
return iterator(this, sentinel.prv);
}
const_iterator end() const
{
return const_iterator(this, &sentinel);
}
iterator end()
{
return iterator(this, &sentinel);
}
T& front() // illegal if is_empty() is true
{
return sentinel.nxt->get_data();
}
T& back() // illegal if is_empty() is true
{
return sentinel.prv->get_data();
}
size_t size() const
{
return count;
}
bool is_empty() const
{
return count==0;
}
void insert_first(const T& x)
{
LLNode * add=alloc_node(x);
insert(&sentinel->nxt, add);
}
void insert_last(const T& x)
{
LLNode * add=alloc_node(x);
insert(&sentinel, add);
}
void erase_first() // illegal if is_empty() is true
{
remove(sentinel.nxt);
}
void erase_last() // illegal if is_empty() is true
{
remove(sentinel.prv);
}
private:
LLNode * alloc_node(const T& x)
{
auto ret = my_traits::allocate(mem,1);
my_traits::construct(mem, &(ret->data), x);
return ret;
}
void unalloc_node(LLNode * dead)
{
my_traits::deallocate(mem, dead, 1);
}
void insert(LLNode * after, LLNode * added)
{
LLNode * before=after->prv;
added->prv=before;
added->nxt=after;
before->nxt=added;
after->prv=added;
++count;
}
void remove(LLNode * node) // illegal if is_empty() is true
{
node->prv->nxt=node->nxt;
node->nxt->prv=node->prv;
unalloc_node(node);
--count;
}
void cp(const LinkedList& rhs)
{
mem = rhs.mem;
for (const_iterator i=rhs.begin(); i != rhs.end(); ++i)
{
insert_last(*i);
}
}
void shallow_cp(LinkedList& rhs)
{
if (rhs.count)
{
count=rhs.count;
sentinel=rhs.sentinel; // shallow copy
// fix the links to the old sentinel
sentinel.nxt.prv=&sentinel;
sentinel.prv.nxt=&sentinel;
rhs.init();
}
}
void rm()
{
LLNode * run=sentinel.nxt;
while (run != &sentinel)
{
LLNode * dead=run;
run=run->nxt;
unalloc_node(dead);
}
}
void init()
{
count=0;
sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
}
LLNode sentinel;
size_t count;
my_alloc mem;
};
有什么遗漏/错误吗?
答案 0 :(得分:2)
似乎使用std :: unique_ptr是有道理的,但仍需要其他指针“共享”unique_ptr。即如果nxt是unique_ptr,我该如何处理prv和iterator :: it?
别。 (1)它使各种内部算法更难以执行而不会意外地删除节点,并且(2)unique_ptr存储删除器,在你的情况下是(A)迭代器的副本,或(B)指向迭代器的指针。任何一个都是浪费空间。容器应存储分配器,容器应处理删除。
它似乎应该将分配器作为模板paraemter,但它将是T类型的分配器,而不是LLNode? p>
容器采用类型为T的分配器,尽管它们都在内部使用了反弹类型。标准是容器的分配器采用类型T
,这样每个std::vector<T, allocator<?>>
都匹配。此外,外人不应该能够访问LLNode。你可能会在内部存储一个given_allocator<LLNode>
。这就是我们重新绑定的原因。
是否有任何特征需要“注册”?
对于容器,没有。有匹配的接口,但那些相对明显。
是的,你的迭代器应该注册特征,但只需在前面添加五个typedef即可轻松完成。
typedef ptrdiff_t difference_type; //usually ptrdif_t
typedef T value_type; //usually T
typedef T& reference; //usually T&
typedef T* pointer; //usually T*
typedef std::bidirectional_iterator_tag iterator_category;
如果使用移动语义,应该定义哪些方法,例如operator =()&amp;&amp;等?
显然,容器应该是可构造的,并且如果有意义的话可以移动分配,它在99.99%的时间内完成。甚至std::array
也有移动运算符。还要确定哪些成员函数应该支持move-only-T(通过迭代器插入范围,但不插入范围+计数),以及哪些成员函数支持任何 T(emplace one)。
需要纠正哪些未定义的行为(如果有的话)?
当标准是在迭代器之前插入时,您的insert(iterator, data)
会在迭代器之后插入数据。您的方式使得无法在开头添加数据,并且可以在后端添加数据。
您的迭代器有但不需要remove
和insert
函数。我不会提到它,但在这种情况下,它们需要迭代器的大小是所需的两倍。我会提出一个暂时的建议来删除它们,但只是试探性的。这是一个小小的惩罚,可能是一个有用的功能。
您的operator =
取消分配所有内容,然后重新分配。在可能的情况下简单地逐个元素跳过复制可能很方便。尽管如此诡计多端。
您缺少从一对迭代器构造的构造函数。
您的迭代器允许变更来自const
容器的数据。这一件事很难
get_first
和get_last
通常称为front
和back
。
我不应该关心我应该做什么?
每个人都忽视的最大问题是异常安全。您的代码是异常安全,但这似乎是因为您的代码非常简单,并且您跳过了所有“硬”部分:P
有关更多信息,请查看以下内容:
Writing your own STL Container
How to implement an STL-style iterator and avoid common pitfalls?
缺少:
T&&
和一个const T&
不正确:
U&&
并使用std::forward<U>(x)
。变种模板会更好。缺少但非常不重要
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
可选:
my_alloc
和my_traits
并且它们相同。