避免std :: map :: find()的关键构造

时间:2012-05-10 14:59:58

标签: c++ c++11

假设我有std::map<std::string, int>std::string可以与没有std::string临时值的C字符串(const char *)进行比较。但是,map::find()似乎迫使我构造一个临时的std::string,这可能需要内存分配。我该如何避免这种情况?从概念上讲,它很容易,但STL似乎阻止了这一点。

#include <map>

int main()
{
    std::map<std::string, int> m;
    m.find("Olaf");
}

4 个答案:

答案 0 :(得分:10)

您的担忧是真实的,C ++ 11没有好的解决方法。

C ++ 14通过添加std::map::find的模板重载来解决此问题 - 相关提案为N3657。在C ++ 14中,您的程序将如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <algorithm>

class std_string {
    char *m_s;
public:
    std_string() { m_s = nullptr; }
    std_string(const char* s) { puts("Oops! A new std_string was constructed!"); m_s = strdup(s); }
    ~std_string() { free(m_s); }
    std_string(std_string&& ss) = delete;
    std_string(const std_string& ss) = delete;
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;

    bool operator< (const char* s) const { return strcmp(m_s, s) < 0; }
    bool operator< (const std_string& ss) const { return strcmp(m_s, ss.m_s) < 0; }
    friend bool operator< (const char* s, const std_string& ss) { return strcmp(s, ss.m_s) < 0; }
};

int main()
{
    {
        puts("The C++11 way makes a copy...");
        std::map<std_string, int> m;
        auto it = m.find("Olaf");
    }
    {
        puts("The C++14 way doesn't...");
        std::map<std_string, int, std::less<>> m;
        auto it = m.find("Olaf");
    }
}

std::less<>是广义的&#34;小于&#34;比较器,相当于operator<。C ++ 03和C ++ 11有一个破解设计的版本这个比较器强制两个参数都是相同的类型.C ++ 14最终做对了。)

不幸的是,委员会似乎已经决定人们应该通过他们所有的C ++ 11代码并更新每个容器以使用std::less<>作为比较器 - 它默认情况下不会发生。这个决定没有充分的理由;它只是它的方式。 (请参阅我上面关于设计破坏的评论.C ++有一个坏习惯,在几年后引入&#34;真正的&#34;版本之前引入破碎的版本。)

对于C ++ 11,std::map::find只有一个重载(采用const Key&的重载),因此任何解决方法都必然涉及将Key类型更改为更便宜 - 我们不能只是摆弄比较器,因为当执行到达比较器时,我们已经将find的参数提升为Key类型。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <algorithm>

class std_string {
    char *m_s;
public:
    std_string() : m_s(nullptr) { }
    std_string(const char* s) : m_s(strdup(s)) { puts("Oops! A new std_string was constructed!"); }
    ~std_string() { free(m_s); }
    std_string(std_string&& ss) : m_s(nullptr) { std::swap(m_s, ss.m_s); }
    std_string(const std_string& ss) : m_s(strdup(ss.data())) { puts("Oops! A new std_string was constructed!"); }
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;

    const char* data() const { return m_s; }

    bool operator< (const char* s) const { return strcmp(m_s, s) < 0; }
    bool operator< (const std_string& ss) const { return strcmp(m_s, ss.m_s) < 0; }
    friend bool operator< (const char* s, const std_string& ss) { return strcmp(s, ss.m_s) < 0; }
};

struct string_or_ptr {
    union {
        const char* ptr;
        alignas(std_string) unsigned char str[sizeof (std_string)];
    } m_u;
    bool m_deep;

    char const* & ptr() { return m_u.ptr; }
    std_string& str() { return *reinterpret_cast<std_string*>(m_u.str); }
    char const* const & ptr() const { return m_u.ptr; }
    std_string const& str() const { return *reinterpret_cast<const std_string*>(m_u.str); }

    string_or_ptr() : m_deep(false) { ptr() = ""; }
    string_or_ptr(const char* s) : m_deep(false) { ptr() = s; }
    string_or_ptr(std_string&& s) : m_deep(true) { new ((void*)&str()) std_string(std::move(s)); }
    string_or_ptr(const std_string& s) : m_deep(true) { new ((void*)&str()) std_string(s); }
    ~string_or_ptr() { if (m_deep) str().~std_string(); }
    std_string& operator=(std_string&& ss) = delete;
    std_string& operator=(const std_string& ss) = delete;


    operator const char*() const { return m_deep ? str().data() : ptr(); }

    bool operator< (const char* s) const { return strcmp((const char*)*this, s) < 0; }
    bool operator< (const std_string& ss) const { return (const char*)*this < ss; }
    bool operator< (const string_or_ptr& sp) const { return strcmp((const char*)*this, (const char*)sp) < 0; }
    friend bool operator< (const char* s, const string_or_ptr& sp) { return strcmp(s, (const char*)sp) < 0; }
    friend bool operator< (const std_string& ss, const string_or_ptr& sp) { return ss < (const char*)sp; }
};

int main()
{
    {
        puts("The C++11 way...");
        std::map<std_string, int> m;
        auto it = m.find("Olaf");
    }
    {
        puts("The C++11 way with a custom string-or-pointer Key type...");
        std::map<string_or_ptr, int> m;
        auto it = m.find("Olaf");
    }
}

答案 1 :(得分:2)

实际上没有办法强制find使用与用于创建map的运算符不同的比较运算符。如果你可以将另一个传递给find,那怎么能保证两个比较都能提供相同的顺序呢?

相反,只需考虑案例:

1)你在程序中传递char*。在这种情况下,就是不要这样做。请改为使用std::string,如果需要,可以创建一次,尽可能接近原点。然后不需要转换。

2)你正试图找到字符串文字。在这种情况下,为什么密钥是string?使密钥成为一个名称很好的枚举类型:

enum names { OLAF };
map<names, int> m;
m.find(OLAF);

3)你想找到两个字符串和C字符串文字。在这种情况下,我将创建一个由枚举索引的字符串的全局查找表,但在main的开头构建一次。然后你会打电话给m.find(global_strings[OLAF]);

编辑:您似乎非常关注/关注string在这里的性能影响。您是否对应用程序进行了分析,发现string的分配是应用程序时间的重要部分?我当然会相信嵌入式系统/设备。

此外,你已经标记了你的问题C ++,但你似乎完全拒绝使用C ++的内置字符串功能,这远远超过“花费你的性能”。它提供了各种有用的功能/方法/操作符,但最重要的是它为您管理内存,因此您不必花费数天或数周的时间来查找那些毫无疑问会出现的真正阴险的错误。

如果您正在从网络中读取可变长度数据,我无法完全理解char* buffer = new char[needed_size];std::string s; s.resize(needed_size);之类的效果差异,而不是使用string提供一些安全性和内存管理。

答案 2 :(得分:1)

如果从文字构造一个字符串确实是一个测量性能瓶颈,你可以使用自己的类而不是一个std::string来保存字符串或指向文字的指针。缺点是一些额外的复杂功能,加上指向要插入容器的元素的指针大小。请注意,map所需的值是不可变的,因此可以安全地存储c_str的结果。

class mystring
{
    std::string  str;
    const char * value;
public:
    mystring() : value(NULL)
    {
    }
    void setString(const std::string & s)
    {
        assert(value == NULL);
        str = s;
        value = str.c_str();
    }
    void setLiteral(const char * s)
    {
        assert(value == NULL);
        value = s;
    }
    bool operator<(const mystring & rhs)
    {
        return strcmp(literal, rhs.literal) < 0;
    }
};

std::map<mystring, int> m;
mystring text;
text.setString(some_key);
m.insert(std::make_pair(text, some_data));
// ...
mystring key;
key.setLiteral("Olaf");
m[key] = new_value;

答案 3 :(得分:0)

没有办法专门为map::find()函数定义比较器。相反,我会建议你使用创建你的比较器(myOwnCmp)和delcare std::map<char*, int, myOwnCmp>为你的程序。对于非常大的程序或当测试用例的数量非常高时,它将比std::map<string, int>快得多,因为通过调用字符串构造函数并稍后调用其析构函数来创建字符串所花费的时间会消耗大量时间。使用const char *作为键只会涉及指针比较。

你唯一需要注意的是当你通过覆盖插入函数或创建自己的add函数来填充地图时,将char *的单独本地副本作为参数,因为有机会稍后可以修改或删除指针。因此,在将其添加为地图的关键字之前,您需要确保保留了char *的本地副本。

代码会是这样的: -

 struct myOwnComp {
        bool operator()(const char* a, const char* b) const {
            return (strcmp(a, b) < 0);
        }
    };

std::map<char*, int, myOwnComp> mymap;

void addToMap(char*& ref, int value)
{
    char *key = new char[strlen(ref) + 1]{};
    std::copy(ref, ref + strlen(ref), key);
    mymap[key] = value;
}