请考虑以下代码:
std::map<std::string, int> m1;
auto i = m1.find("foo");
const char* key = ...
auto j = m1.find(key);
每次进行map查找都会创建一个临时的std::string对象。有哪些常用方法可以避免这种情况?
void do_something(std::string const & key)
{
auto it = m.find(key);
// ....
}
在使用C++时,越多地使用其特有的习惯用法而不是试图将其用作C语言,通常会让代码变得更加"正确"。
m1.find("foo")
创建临时对象的问题(即使最终无法绕过此临时对象)。 - André Caronstd::string const foo_string("foo")
并使用它即可... - Kerrek SBstd::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 *。请记住,您在映射中存储的任何内容必须在映射的生命周期内保持活动状态。这意味着您需要使用字符字面量,或者您必须自己维护指针指向的数据保持活动状态。由于这些原因,我建议使用std::map<std::string>
并忽略临时解决方案,直到性能分析表明确实需要上述方法。
std::map<std::string,int,StrCmp>
,但你的例子并没有。你想要表达什么意思? - mabraham无法避免复制字符数据的临时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
类型,类似于std::nothrow
用于放置new的构造函数)。
cheap_string
的定义,一旦临时变量被删除,它不会尝试(错误地)释放 "foo"
吗? - Thanatosproxy()
方法使用了自定义的删除器abandon()
,它是一个空操作,从不调用delete。让我详细解释一下。 - André Caron嗯,map的find
实际上接受键的常量引用,所以你无论如何都不能避免创建它。
对于代码的第一部分,您可以使用具有值“foo”的常量静态std :: string来查找。这样就不会创建副本。
如果您想采用Spartan的方式,可以始终创建自己的类型,该类型可像字符串一样使用,但也可以保存指向字符串文字的指针。
但是无论如何,与map查找相关的开销非常巨大,因此这真的没有意义。如果我是您,我将首先用google的dense hash替换map / unordered_map。然后我会运行Intel's Vtune(现在是放大器)并查看时间去了哪里,优化那些地方。我怀疑字符串作为键是否会出现在瓶颈前十名列表中。
看一下llvm中的StringRef类。
它们可以非常便宜地从c-strings、字符串字面量或std::string构造。如果你用这些构建一个映射,而不是std::string,那么构建速度会非常快。
但这是一个非常脆弱的系统。你需要确保插入的字符串来源在映射的生命周期内保持活动和未修改。
char const *
。 - Pabloconst char*
? - Puppychar const*
,而是想改变比较器以允许存储std::string
并与char const*
进行比较,而不需要创建临时的std::string
。 - Pablo