我想知道包装C++ STL容器以保持一致性并且能够在不修改客户端代码的情况下交换实现是一个好主意.
例如,在一个项目中,我们使用CamelCase命名类和成员函数(Foo :: DoSomething()),我会将std :: list包装成这样的类:
template<typename T>
class List
{
public:
typedef std::list<T>::iterator Iterator;
typedef std::list<T>::const_iterator ConstIterator;
// more typedefs for other types.
List() {}
List(const List& rhs) : _list(rhs._list) {}
List& operator=(const List& rhs)
{
_list = rhs._list;
}
T& Front()
{
return _list.front();
}
const T& Front() const
{
return _list.front();
}
void PushFront(const T& x)
{
_list.push_front(x);
}
void PopFront()
{
_list.pop_front();
}
// replace all other member function of std::list.
private:
std::list<T> _list;
};
Run Code Online (Sandbox Code Playgroud)
然后我就能写出这样的东西:
typedef uint32_t U32;
List<U32> l;
l.PushBack(5);
l.PushBack(4);
l.PopBack();
l.PushBack(7);
for (List<U32>::Iterator it = l.Begin(); it != l.End(); ++it) {
std::cout << *it << std::endl;
}
// ...
Run Code Online (Sandbox Code Playgroud)
我相信大多数现代C++编译器都可以轻松地优化掉额外的间接,我认为这种方法有一些优点,如:
我可以轻松扩展List类的功能.例如,我想要一个对列表进行排序然后调用unique()的速记函数,我可以通过添加成员函数来扩展它:
template<typename T>
void List<T>::SortUnique()
{
_list.sort();
_list.unique();
}
Run Code Online (Sandbox Code Playgroud)
此外,只要行为相同,我就可以交换底层实现(如果需要)而不对它们使用List的代码进行任何更改.还有其它好处,因为它保持了项目中命名约定的一致性,因此对于整个项目中的其他类,STL和PushBack()没有push_back():
std::list<MyObject> objects;
// insert some MyObject's.
while ( !objects.empty() ) {
objects.front().DoSomething();
objects.pop_front();
// Notice the inconsistency of naming conventions above.
}
// ...
Run Code Online (Sandbox Code Playgroud)
我想知道这种方法是否有任何主要(或次要)缺点,或者这是否实际上是一种实用方法.
谢谢.
编辑:好的,感谢目前为止的答案.我想我可能在问题的命名一致性方面投入太多.实际上命名约定不是我关心的,因为一个人也可以提供完全相同的接口:
template<typename T>
void List<T>::pop_back()
{
_list.pop_back();
}
Run Code Online (Sandbox Code Playgroud)
或者甚至可以使另一个实现的接口看起来更像是大多数C++程序员已经熟悉的STL.但无论如何,在我看来,这更像是一种风格,而不是那么重要.
我关心的是能够轻松更改实现细节的一致性.堆栈可以以各种方式实现:阵列和顶部索引,链表或甚至两者的混合,并且它们都具有数据结构的LIFO特性.自平衡二进制搜索树也可以用AVL树或红黑树实现,并且它们都具有用于搜索,插入和删除的O(logn)平均时间复杂度.
因此,如果我有一个AVL树库和另一个具有不同接口的红黑树库,我使用AVL树来存储一些对象.后来,我想(使用分析器或其他)使用红黑树会提高性能,我将不得不去使用AVL树的文件的每个部分,并更改类,方法名称和可能的参数订购其红黑树同行.甚至有些情况下新类没有编写等效的功能.我认为它也可能引入细微的错误,因为实现上的差异,或者我犯了一个错误.
所以我开始怀疑,如果维护这样一个包装类来隐藏实现细节并为不同的实现提供统一的接口是值得的.
template<typename T>
class AVLTree
{
// ...
Iterator Find(const T& val)
{
// Suppose the find function takes the value to be searched and an iterator
// where the search begins. It returns end() if val cannot be found.
return _avltree.find(val, _avltree.begin());
}
};
template<typename T>
class RBTree
{
// ...
Iterator Find(const T& val)
{
// Suppose the red-black tree implementation does not support a find function,
// so you have to iterate through all elements.
// It would be a poor tree anyway in my opinion, it's just an example.
auto it = _rbtree.begin(); // The iterator will iterate over the tree
// in an ordered manner.
while (it != _rbtree.end() && *it < val) {
++it;
}
if (*++it == val) {
return it;
} else {
return _rbtree.end();
}
}
};
Run Code Online (Sandbox Code Playgroud)
现在,我只需要确保AVLTree :: Find()和RBTree :: Find()完全相同(即获取要搜索的值,将迭代器返回到元素或End(),否则).然后,如果我想从AVL树更改为红黑树,我所要做的就是更改声明:
AVLTree<MyObject> objectTree;
AVLTree<MyObject>::Iterator it;
Run Code Online (Sandbox Code Playgroud)
至:
RBTree<MyObject> objectTree;
RBTree<MyObject>::Iterator it;
Run Code Online (Sandbox Code Playgroud)
通过维持两个班级,其他一切都是一样的.
以上只是我想用包装类存档的一个例子.也许这种情况在实际发展中太少了,而且我太担心了,我不知道.这就是我要问的原因.
我已将标题从"包装STL容器"更改为"包装容器以保持一致性"以更准确地反映问题.
谢谢你的时间.
我想知道这种方法是否有任何重大(或次要)缺点,
两个字:维护噩梦.
然后,当你得到一个新的支持移动的C++ 0x编译器时,你将不得不扩展所有的包装器类.
不要误解我的意思 - 如果您需要其他功能,包装STL容器没有任何问题,只是为了"一致的成员函数名称"?开销太大了.投入太多时间没有投资回报率.
我应该补充一点:不一致的命名约定只是你在使用C++时的习惯.在太多可用(和有用)库中存在太多不同的样式.