使用std :: string键避免std :: map / std :: unordered_map查找中的临时值

时间:2012-01-24 04:05:05

标签: c++ stl

请考虑以下代码:

std::map<std::string, int> m1;
auto i = m1.find("foo");

const char* key = ...
auto j = m1.find(key);

这将为每个地图查找创建一个临时的std :: string对象。避免它的规范方法是什么?

5 个答案:

答案 0 :(得分:3)

不要使用指针;相反,直接传递字符串。然后你可以利用参考资料:

void do_something(std::string const & key)
{
    auto it = m.find(key);
    // ....
}

C ++通常变得“更正确”,你使用它的习语越多,不要尝试用它来写C。

答案 1 :(得分:1)

您可以通过为std::map提供一个可以比较char *的自定义比较器类来避免临时性。 (默认情况下将使用指针的地址,这不是你想要的。你需要比较字符串的值。)

因此,像:

class StrCmp
{
public:
  bool operator () (const char *a, const char *b)
  {
    return strcmp(a, b) < 0;
  }
};

// Later:
std::map<const char *, int, StrCmp> m;

然后,像普通地图一样使用,但传递char *。请注意,您在地图中存储的任何内容必须在地图期间保持活动状态。这意味着你需要char文字,或者你必须保持指针所指向的数据。出于这些原因,我会选择std::map<std::string>并暂时吃掉,直到剖析表明真的需要上面的那些。

答案 2 :(得分:1)

无法避免复制字符数据的临时std::string实例。请注意,如果标准库实现使用短字符串优化,则此成本非常低,并且不会产生动态内存分配。

但是,如果您需要频繁代理C风格的字符串,您仍然可以提出绕过此分配的自定义解决方案。如果您必须经常执行此操作非常,这可以获得回报,并且您的字符串足够长,不会受益于短字符串优化。

如果你只需要非常小的字符串功能子集(例如只有赋值和副本),那么你可以编写一个小的专用字符串类来存储const char *指针和释放记忆的功能。

 class cheap_string
 {
 public:
     typedef void(*Free)(const char*);
 private:
     const char * myData;
     std::size_t mySize;
     Free myFree;
 public:
     // direct member assignments, use with care.
     cheap_string ( const char * data, std::size_t size, Free free );

     // releases using custom deleter (a no-op for proxies).
     ~cheap_string ();

     // create real copies (safety first).
     cheap_string ( const cheap_string& ); 
     cheap_string& operator= ( const cheap_string& ); 
     cheap_string ( const char * data );
     cheap_string ( const char * data, std::size_t size )
         : myData(new char[size+1]), mySize(size), myFree(&destroy)
     {
         strcpy(myData, data);
         myData[mySize] = '\0';
     }

     const char * data () const;
     const std::size_t size () const;

     // whatever string functionality you need.
     bool operator< ( const cheap_string& ) const;
     bool operator== ( const cheap_string& ) const;

     // create proxies for existing character buffers.
     static const cheap_string proxy ( const char * data )
     {
          return cheap_string(data, strlen(data), &abandon);
     }

     static const cheap_string proxy ( const char * data, std::size_t size )
     {
          return cheap_string(data, size, &abandon);
     }

 private:
     // deleter for proxies (no-op)
     static void abandon ( const char * data )
     {
         // no-op, this is used for proxies, which don't own the data!
     }

     // deleter for copies (delete[]).
     static void destroy ( const char * data )
     {
         delete [] data;
     }
 };

然后,您可以将此类用作:

 std::map<cheap_string, int> m1;
 auto i = m1.find(cheap_string::proxy("foo"));

临时cheap_string实例不像std::string那样创建字符缓冲区的副本,但它保留了用于在标准容器中存储cheap_string实例的安全复制语义。

备注:如果您的实现不使用返回值优化,您将需要找到proxy方法的替代语法,例如具有特殊重载的构造函数(取自定义proxy_t类型àlastd::nothrow,用于展示新位置。

答案 3 :(得分:0)

好吧,地图的find实际上接受了对该键的常量引用,因此您无法避免在某个时刻创建它。

对于代码的第一部分,您可以使用值为“foo”的常量静态std :: string进行查找。这样你就不会创建副本了。

如果你想使用Spartan的方式,你总是可以创建自己的类型,可以像字符串一样使用,但也可以保存指向字符串文字的指针。

但无论如何,与地图查找相关的开销是如此巨大,所以这并没有多大意义。如果我是你,我首先用谷歌的密集哈希替换map / unordered_map。然后我将运行英特尔的VTune(这些天放大器),看看时间在哪里并优化这些地方。我怀疑字符串,因为键会出现在瓶颈十大名单中。

答案 4 :(得分:0)

查看来自llvm的StringRef课程。

它们可以从c-strings,string literals或std :: string构建得非常便宜。如果你制作了这些的地图,而不是std :: string,那么构造速度会非常快。

但这是一个非常脆弱的系统。您需要确保无论您插入的字符串的来源在地图的生命周期内保持活动和未修改。