C++ STL 集的复杂模板参数类型
Posted
技术标签:
【中文标题】C++ STL 集的复杂模板参数类型【英文标题】:complex template parameter type for C++ STL set 【发布时间】:2011-08-27 06:05:12 【问题描述】:我正在实现一个具有复杂模板参数类型的 STL 集。插入集合时,我希望集合使用我为我的类型定义的小于运算符。我也想尽量减少我的类型的对象实例化的数量。看来我不能两者兼得。
下面有两个最小的示例,每个都使用相同的 C++ 类。
#include <iostream>
#include <set>
using namespace std;
class Foo
public:
Foo(int z);
Foo(const Foo &z);
bool operator<(const Foo &rhs) const;
int a;
;
Foo::Foo(int z)
cout << "cons" << endl;
a = z;
Foo::Foo(const Foo &z)
cout << "copy cons" << endl;
a = z.a;
bool
Foo::operator<(const Foo &rhs) const
cout << "less than" << endl;
return a < rhs.a;
这是我的第一个 main():
int
main(void)
set<Foo> s;
s.insert(*new Foo(1));
s.insert(*new Foo(2));
s.insert(*new Foo(1));
cout << "size: " << s.size() << endl;
return 0;
这很好,因为它使用了我为我的班级定义的小于,因此集合的大小正确地是两个。但这很糟糕,因为每次插入集合都需要实例化两个对象(构造函数、复制构造函数)。
$ ./a.out
cons
copy cons
cons
less than
less than
less than
copy cons
cons
less than
less than
less than
size: 2
这是我的第二个 main():
int
main(void)
set<Foo *> s;
s.insert(new Foo(1));
s.insert(new Foo(2));
s.insert(new Foo(1));
cout << "size: " << s.size() << endl;
return 0;
这很好,因为插入只需要一个对象实例化。但这很糟糕,因为它实际上是一组指针,因此就我的类型而言,集合成员的唯一性已经消失。
$ ./a.out
cons
cons
cons
size: 3
我希望我缺少一些信息。我是否可以同时拥有最少的对象实例化和适当的排序?
【问题讨论】:
为什么你认为复制构造函数很昂贵而使用运算符new
不是?
贵吗?我不明白你的问题(也许你不明白我的)。我的问题是 both 每次插入都会调用一个复制构造函数和构造函数(两个对象)。我不在乎哪个更贵。两者的总和是我试图避免的。
我会尽量避免使用new
,因为我知道那很昂贵。可能比额外的构造函数更昂贵。
【参考方案1】:
您正在从这里获得一份副本:*new Foo(1)
。
创建这个结构:
template<typename T>
struct PtrLess
bool operator()(const T *a, const T *b) const
return *a < *b;
;
使地图看起来像 set<Foo*, PtrLess<Foo>> s;
,然后添加 Foo 像 s.insert(new Foo(1));
注意 *
否则,当映射为 Foo 项创建容器时,由于它是在 foo 容器定义中分配的,因此映射必须将提供的值复制到其内部 Foo 对象中。
【讨论】:
Mranz,我认为您没有阅读整个问题。您的建议不起作用,因为您失去了排序。 抱歉,我忘记了其中的一部分。我修改了答案。 @Mranz 这样解决了插入Foo*
时的比较问题,但是内存泄漏问题呢?
正如 Mranz 下面提到的,我将在集合超出范围之前根据需要删除对象。没有内存泄漏。
假设在集合离开范围之前的某个地方,您迭代集合并删除,建议的解决方案没有固有的内存泄漏。【参考方案2】:
标准容器存储添加的项目的副本。如果您希望您的set
存储对象,而不是指针,您应该简单地执行以下操作,否则您正在创建内存泄漏,因为通过new
分配的对象永远不会通过相应的delete
释放。
int main()
set<Foo> s;
s.insert(Foo(1));
s.insert(Foo(2));
s.insert(Foo(1));
cout << "size: " << s.size() << endl;
return 0;
如果您想最小化实例化的临时对象的数量,只需使用单个临时对象:
int main()
set<Foo> s;
Foo temp(1);
s.insert(temp);
temp.a = 2;
s.insert(temp);
temp.a = 1;
s.insert(temp);
cout << "size: " << s.size() << endl;
return 0;
这个 sn-p 的输出(通过ideone
)是:
cons
copy cons
less than
less than
less than
copy cons
less than
less than
less than
size: 2
一般来说,我更愿意将实际对象存储在set<Foo>
中而不是指向set<Foo*>
中的对象的指针,因为对象所有权不会有任何问题(谁/何时需要new
和delete
被调用),分配的内存总量更小(对于N
项目,您需要N*sizeof(Foo)
而不是N*(sizeof(Foo) + sizeof(Foo*))
字节)并且数据访问可能 通常预计会更快(因为没有额外的指针间接)。
希望这会有所帮助。
【讨论】:
只有在集合离开作用域时指针没有被删除,才会出现内存泄漏。我认为这是一个人为的例子,因为大多数真实程序不包含 Foo 类。 @Mranz:我说的是第一个例子,其中集合是std::set<Foo>
。在这种情况下,临时对象是通过*new Foo(1) etc
创建的,它是堆上的allocating/constructing
新对象,但从不存储指向这些对象的指针。那么就没有办法将delete
打电话给destruct/free
他们。这是内存泄漏...
啊,是的,你完全正确。我忘记了那部分:)【参考方案3】:
这是@Mranz 的answer 的扩展。不要处理原始指针,而是将指针放在std::unique_ptr
#include <memory>
using namespace std;
template<typename T>
struct PtrLess
bool operator()(const T& a, const T& b) const
return *a < *b;
;
int
main(void)
set<unique_ptr<Foo>, PtrLess<unique_ptr<Foo>>> s;
s.insert(unique_ptr<Foo>(new Foo(1)));
s.insert(unique_ptr<Foo>(new Foo(2)));
s.insert(unique_ptr<Foo>(new Foo(1)));
cout << "size: " << s.size() << endl;
return 0;
【讨论】:
以上是关于C++ STL 集的复杂模板参数类型的主要内容,如果未能解决你的问题,请参考以下文章
C++ 标准模板库STL 队列 queue 使用方法与应用介绍
C++ STL学习 —— 模板泛型算法函数对象lambda 表达式(参数捕获)函数适配器