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

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

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

#include <map>

int main()
{
    std::map<std::string, int> m;
    m.find("Olaf");
}
#包括
int main()
{
std::map m;
m、 查找(“Olaf”);
}

实际上没有办法强制
查找
使用不同于创建
映射所用的比较运算符。如果您可以将另一个传递到
find
,它如何保证两个比较将提供相同的顺序

相反,只要想想这些案例:

1) 您正在程序中传递
char*
。在这种情况下,不要这样做。改用
std::string
,必要时创建一次,尽可能靠近原始文件。那么就不需要转换了

2) 您正在尝试查找字符串文字。在这种情况下,为什么键是字符串?将密钥改为命名良好的枚举类型:

enum names { OLAF };
map<names, int> m;
m.find(OLAF);
enum名称{OLAF};
地图m;
m、 查找(OLAF);
3) 您希望同时查找字符串和C字符串文本。在本例中,我将创建一个由枚举索引但在main开头构建一次的字符串的全局查找表。然后调用类似于
m.find(global_strings[OLAF])的东西

编辑:您似乎非常关注
string
的性能影响。您是否分析过您的应用程序,并发现
string
的分配占了应用程序时间的很大一部分?我当然相信这是在嵌入式系统/设备上实现的

另外,你已经标记了你的问题C++,但是你似乎完全拒绝使用C++的内置字符串特性,它远远超过了“性价比”。它提供了各种有用的函数/方法/运算符,但最重要的是它为您管理内存,这样您就不会花费数天或数周的时间来查找那些毫无疑问会突然出现的真正潜在的bug


如果您正在从网络读取可变长度数据,我无法完全理解
char*buffer=new char[needed_size]之间的性能差异和类似于
std::string s的东西;s、 调整大小(所需大小)string
之外,code>还为您提供了一些安全性和内存管理。

如果从文本构造字符串对您来说确实是一个性能瓶颈,那么您可以使用自己的类,而不是保存字符串或指向文本的指针的
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;
类mystring
{
std::字符串str;
常量字符*值;
公众:
mystring():值(NULL)
{
}
无效设置字符串(常量std::string&s)
{
断言(值==NULL);
str=s;
value=str.c_str();
}
void setLiteral(常量字符*s)
{
断言(值==NULL);
值=s;
}

bool操作符您关心的是真实的,对于C++11没有好的解决方法

C++14通过添加模板重载
std::map::find
,解决了此问题-相关建议如下。在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");
    }
}

没有办法专门为
map::find()
函数定义比较器。相反,我建议您使用创建比较器(myowncp)和delcare a
std::map
用于您的程序。对于非常大的程序或当测试用例数非常高时,它将比
std::map
更快,因为通过调用字符串构造函数和随后调用其析构函数来创建字符串需要花费大量时间。使用const char*因为键只涉及指针比较

您需要注意的唯一一件事是,通过重写insert函数或创建自己的add函数来填充映射时,为作为参数的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;
}
struct myOwnComp{
布尔运算符()(常量字符*a,常量字符*b)常量{
返回(strcmp(a,b)<0);
}
};
std::map mymap;
void addToMap(字符*&ref,int值)
{
char*key=newchar[strlen(ref)+1]{};
标准::副本(参考,参考+strlen(参考),键);
mymap[key]=值;
}

如果你使用的是
string
键的地图,那么在封面下和构建地图时,很多不可见的
string
分配已经发生了。这真的值得担心吗?大多数应用都有其他性能问题,排名更高。你可以通过在静态模式中隔离魔法值来避免这种情况“支原体“类可能会发生。@SteveTownsend大多数/所有这些分配都将发生在insert上,但是,如果您有一个查询映射的高性能方法,但没有执行分配,这可能很简单…@Benj-
map::find
无论如何都不会导致明显的分配,除非输入需要
string
构造(在这里,通过隐式转换)@Steve:它们不是不可见的,也可能不是很多。它是每个键一个分配(带有移动语义)。如果你真的想努力,你可以围绕std::map编写一个包装器,只为string编写,并为find提供两个方法。一个用于std::string,另一个用于
 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;
}