25

假设我有一个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

4 回答 4

15

您的担忧是真实的,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<>是广义的“小于”比较器,等同于operator<.C++03 和 C++11 具有此比较器的设计破坏版本,强制两个参数为相同类型。C++14 最终做到了对的。)

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

对于 C++11,std::map::find只有一个重载(采用 的那个const Key&),因此任何解决方法都必然涉及将Key类型更改为更便宜 - 我们不能只是摆弄比较器,因为当执行到达比较器时,我们已经将find's 参数提升为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");
    }
}
于 2014-06-23T07:25:33.597 回答
2

实际上没有办法强制find使用与用于创建map. 如果您可以将不同的传递给find,它如何保证两个比较将提供相同的顺序?

相反,只需考虑以下情况:

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

2)您正在尝试查找字符串文字。在这种情况下,为什么密钥是 a 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

于 2012-05-10T15:15:19.573 回答
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;
于 2012-05-10T15:49:07.730 回答
0

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

您唯一需要注意的是,当您通过覆盖插入函数或创建自己的添加函数来填充地图时,创建一个单独的 char* 本地副本作为参数,因为指针有可能以后可以修改或删除。因此,您要确保在将 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;
}
于 2018-06-01T07:49:44.413 回答