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&lt;Foo*, PtrLess&lt;Foo&gt;&gt; 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&lt;Foo&gt; 中而不是指向set&lt;Foo*&gt; 中的对象的指针,因为对象所有权不会有任何问题(谁/何时需要newdelete被调用),分配的内存总量更小(对于N 项目,您需要N*sizeof(Foo) 而不是N*(sizeof(Foo) + sizeof(Foo*)) 字节)并且数据访问可能 通常预计会更快(因为没有额外的指针间接)。

希望这会有所帮助。

【讨论】:

只有在集合离开作用域时指针没有被删除,才会出现内存泄漏。我认为这是一个人为的例子,因为大多数真实程序不包含 Foo 类。 @Mranz:我说的是第一个例子,其中集合是std::set&lt;Foo&gt;。在这种情况下,临时对象是通过*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 的使用方法

STL 容器类型作为模板参数

C++ 标准模板库STL 队列 queue 使用方法与应用介绍

C++ STL学习 —— 模板泛型算法函数对象lambda 表达式(参数捕获)函数适配器

C++ STL学习 —— 模板泛型算法函数对象lambda 表达式(参数捕获)函数适配器

C++ STL学习 —— 模板泛型算法函数对象lambda 表达式(参数捕获)函数适配器