我有一张代表DB对象的地图。我希望从中获得“众所周知”的价值
std::map<std::string, std::string> dbo;
...
std::string val = map["foo"];
一切都很好,但是每次通话都会将“foo”转换为临时字符串。当然,拥有一个常量的std :: string会更好(当然,与刚刚获取对象的磁盘IO相比,它可能只是一个很小的开销,但我认为它仍然是一个有效的问题)。那么std :: string常量的正确习惯是什么?
例如 - 我可以
const std::string FOO = "foo";
在hdr中,但后来我得到了多份副本
编辑:还没有回答说如何声明std :: string常量。忽略整个地图,STL等问题。很多代码都是以std :: string为导向的(我当然是这样)并且很自然地需要为它们设置常量而不需要为内存分配反复付费
EDIT2:从Manuel那里拿出了PDF回答的二级问题,添加了一个不好习惯的例子
EDIT3:答案摘要。请注意,我没有包含那些建议创建新字符串类的内容。我很失望,因为我希望有一个简单的东西只能在头文件中工作(如const char * const)。反正
a)来自Mark b
std::map<int, std::string> dict;
const int FOO_IDX = 1;
....
dict[FOO_IDX] = "foo";
....
std:string &val = dbo[dict[FOO_IDX]];
b)来自vlad
// str.h
extern const std::string FOO;
// str.cpp
const std::string FOO = "foo";
c)来自Roger P
// really you cant do it
(b)似乎与我想要的最接近,但有一个致命的缺陷。我不能拥有使用这些字符串的静态模块级代码,因为它们可能尚未构建。我想过(a)并且实际上在序列化对象时使用类似的技巧,发送索引而不是字符串,但对于通用解决方案来说似乎有很多管道。很遗憾(c)获胜,std:string 没有简单的常用语法
答案 0 :(得分:17)
复制和缺少“字符串文字优化”只是std :: strings的工作原理,你无法得到你所要求的。部分原因是因为明确避免了虚方法和dtor。无论如何,std :: string接口的很多很复杂。
标准要求std :: string和std :: map都有一个特定的接口,这些接口恰好不允许你想要的优化(作为其他要求的“意外结果”,而不是明确的)。至少,如果你想真正遵循标准的所有细节,他们不允许它。而且你确实想要这样,特别是当这种特定优化使用不同的字符串类时非常容易。
然而,单独的字符串类可以解决这些“问题”(正如你所说,这很少是一个问题),但不幸的是,世界已经有number_of_programmers + 1
个问题。即使考虑到轮重新发明,我发现有一个StaticString类很有用,它有一个std :: string接口的子集:using begin / end,substr,find等。它也不允许修改(并适合字符串文字)那样),只存储一个字符指针和一个大小。你必须要小心,它只是用字符串文字或其他“静态”数据初始化,但这在某种程度上可以通过构造界面来缓解:
struct StaticString {
template<int N>
explicit StaticString(char (&data)[N]); // reference to char array
StaticString(StaticString const&); // copy ctor (which is very cheap)
static StaticString from_c_str(char const* c_str); // static factory function
// this only requires that c_str not change and outlive any uses of the
// resulting object(s), and since it must also be called explicitly, those
// requirements aren't hard to enforce; this is provided because it's explicit
// that strlen is used, and it is not embedded-'\0'-safe as the
// StaticString(char (&data)[N]) ctor is
operator char const*() const; // implicit conversion "operator"
// here the conversion is appropriate, even though I normally dislike these
private:
StaticString(); // not defined
};
使用:
StaticString s ("abc");
assert(s != "123"); // overload operators for char*
some_func(s); // implicit conversion
some_func(StaticString("abc")); // temporary object initialized from literal
请注意,此类的主要优点是显式避免复制字符串数据,因此可以重用字符串文字存储。这个数据的可执行文件中有一个特殊的位置,它通常都经过了很好的优化,因为它可以追溯到C及以后的最早期。事实上,我认为这个类接近于C ++中应该使用的字符串文字,如果它不符合C兼容性要求。
通过扩展,如果这是一个非常常见的场景,你也可以编写自己的地图类,这可能比更改字符串类型更容易。
答案 1 :(得分:9)
很简单:使用
extern const std::string FOO;
标题中的和
const std::string FOO("foo");
在相应的.cpp
文件中。
答案 2 :(得分:6)
当你想要的只是一个常量字符串时,可以避免创建std::string
的开销。但是你需要为此编写一个特殊的类,因为在STL或Boost中没有类似的东西。或者更好的选择是使用来自Chromium的StringPiece
或来自LLVM的StringRef
这样的类。有关详细信息,请参阅此related thread。
如果您决定继续使用std::string
(您可能会这样做),那么另一个不错的选择是使用Boost MultiIndex容器,该容器具有以下功能(引用the docs):< / p>
Boost MultiIndex [...]提供查找功能 接受搜索键的操作 与key_type不同 index,这是特别有用的 key_type对象的设施 创造成本很高。
Maps with Expensive Keys( C / C ++用户期刊,2006年2月)与您的问题有关,并且阅读非常好。
答案 3 :(得分:1)
正确的习语就是你正在使用的那个。 99.99%的时间不需要担心std :: string的构造函数的开销。
我想知道编译器是否可以将std :: string的构造函数转换为内部函数?从理论上讲,它可能是可能的,但我上面的评论可以解释为什么它没有发生。
答案 4 :(得分:1)
看起来您已经知道字符串文字在运行时会是什么,因此您可以在枚举值和字符串数组之间设置内部映射。然后,您将在代码中使用枚举而不是实际的const char * literal。
enum ConstStrings
{
MAP_STRING,
FOO_STRING,
NUM_CONST_STRINGS
};
std::string constStrings[NUM_CONST_STRINGS];
bool InitConstStrings()
{
constStrings[MAP_STRING] = "map";
constStrings[FOO_STRING] = "foo";
}
// Be careful if you need to use these strings prior to main being called.
bool doInit = InitConstStrings();
const std::string& getString(ConstStrings whichString)
{
// Feel free to do range checking if you think people will lie to you about the parameter type.
return constStrings[whichString];
}
然后你会说map[getString(MAP_STRING)]
或类似的。
另外,如果您不需要修改它,还可以考虑通过const引用存储返回值而不是复制它:
const std::string& val = map["foo"];
答案 5 :(得分:1)
在C ++ 14中,你可以做到
const std::string FOO = "foo"s;
答案 6 :(得分:0)
问题是std::map
将密钥和值复制到自己的结构中。
您可以拥有std::map<const char *, const char *>
,但您必须提供功能对象(或函数)来比较键和值数据,因为此模板用于指针。默认情况下,map
会比较指针而不是指针指向的数据。
权衡是一次性复制(std::string
)与访问比较器(const char *
)。
另一种方法是编写自己的map
函数。
答案 7 :(得分:0)
我认为你要找的是'boost :: flyweight&lt; std :: string&gt; “
这是对共享字符串值的逻辑const引用。非常高效的存储和高性能。
答案 8 :(得分:0)
我的解决方案(具有能够使用以前回答此问题时不存在的C ++ 11功能的优势):
"Paul"
是的,它是一个宏,它可以使用更好的名称。