2010-02-22 33 views
24

我有一张表示DB对象的地图。我想从它std :: string常量的正确用法?

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回答的第二个问题,不良习语的附加例子

编辑3:答案总结。请注意,我没有包括那些建议创建一个新的字符串类。我很失望,因为我希望有一个简单的东西,只能在头文件中工作(如const char * const)。反正

一)从弗拉德

// str.h 
extern const std::string FOO; 
// str.cpp 
const std::string FOO = "foo"; 

C)标记B

std::map<int, std::string> dict; 
const int FOO_IDX = 1; 
.... 
dict[FOO_IDX] = "foo"; 
.... 
std:string &val = dbo[dict[FOO_IDX]]; 

b)由罗杰·P

// really you cant do it 

(二)似乎是最接近我想要的东西但有一个致命的缺陷。我无法使用这些字符串的静态模块级代码,因为它们可能尚未构建。我想过(a)​​,实际上在序列化对象时使用类似的技巧,发送索引而不是字符串,但它似乎是一个通用解决方案的很多管道。所以,可悲的是(C)获胜,有性病不简单 const的成语:字符串

+0

重新编辑,我试图解决这个问题(“不,这不是如何标准::字符串工作”和“这里是另一个字符串类”),但请注意,如果你只是想存储const std ::字符串的地方作为全局变量,你只需要用ref-counting实现(gcc提供的和标准明确允许的)来支付一次(在程序启动时)的分配。只要确保这些全局变量是* const *即可轻松避免各种逻辑错误。 – 2010-02-22 18:41:23

+0

所以你的答案是 - 它不可能。它不明确。如果没有人提出更好的答案,那么你会得到刻度标记 – pm100 2010-02-22 18:43:16

+0

其实我现在喜欢Manuel的答案。抱歉,我不够清楚,这就是为什么我发表评论,但我也偷偷在另一个选项意外。 :) – 2010-02-22 18:54:45

回答

17

复制和缺乏“字符串文字优化”的字符串是多么的std ::工作,你不能得到什么你”再问。部分原因是因为虚拟方法和流程被明确地避免。 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++中的字符串文字。

通过扩展,你也可以编写自己的地图类,如果这对你来说真的很常见,那可能比改变字符串类型更容易。

+12

如果有足够的需求,我可以从零开始重新创建这个类,并将它放在SO的开源许可证下(我通常非常小心)。假设这个评论得到+10票,那么我会做。 :) – 2010-02-22 18:15:09

+1

@RogerPate你现在就挂在了 – 2014-12-27 08:33:16

6
  1. 当所有你想要的是一个常量字符串时,可以避免创建std::string的开销。但是你需要为此写一个特殊的类,因为在STL或Boost中没有类似的东西。或者更好的选择是使用Chromium的StringPiece或LLVM的StringRef。有关更多信息,请参阅此related thread

  2. 如果你决定留在std::string(你可能会)然后另一个很好的选择是使用Boost多指标容器,它具有以下特点(报价the docs):

    升压多指标[。 ..]提供查找 操作接受搜索键 不同于 索引的key_type,这是一个特别有用的 设施,当key_type对象是 创建昂贵。

Maps with Expensive Keys由安德烈Alexandrescu的(C/C++用户期刊,2006年2月),是关系到你的问题,是一个非常不错的读取。

+1

@Manuel你是对的 - pdf是一个完全匹配和有趣的阅读。他并没有讨论经常问的问题 – pm100 2010-02-22 18:37:22

0

问题是std::map将键和值复制到它自己的结构中。

您可能有一个std::map<const char *, const char *>,但您必须提供功能对象(或函数)来比较键和值数据,因为此模板用于指针。默认情况下,map会比较指针,而不是指针指向的数据。

折衷是一次性复制(std::string)与访问比较器(const char *)之间的关系。

另一种选择是编写自己的map函数。

+0

重新看第一行,当然现在不是那么糟糕,因为我们已经移动了语义。 – 2017-02-11 13:11:17

1

正确的成语是你正在使用的。 99.99%的时间没有必要担心std :: string的构造函数的开销。

我不知道std :: string的构造函数是否可以通过编译器变成内在函数?理论上这可能是可能的,但我上面的评论足以解释为什么它没有发生。

+0

更好的是,让优化程序足够先进,以弄清楚如何在字符串的ctor中使用strlen(代码归结为std :: string或wcstring的wcslen)一个字符串文字。更难,真实,我不知道是否有这样做,但它也会比std :: string更有益处。 - 哦,我很傻,这只是战斗的一半,因为你必须复制数据。嗯。 – 2010-02-22 18:57:43

+0

是的,问题在于认识到构造函数正在传递一个静态常量,并且在不复制的情况下将内部存储指针设置为它。标准的类型信息并没有达到那个细节的级别,所以没有特殊的编译器魔法是不可能的。 – 2010-02-22 19:25:09

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"]; 
+0

为什么不使用std :: strings数组? http://codepad.org/6b3JkcJj – 2010-02-22 20:13:42

+1

@ pm100:您可以使用各种代码生成技巧来提前“知道”数据 - 类似于gettext i18n库如何提取字符串;然而,这是很多工作,您可以根据需要简单地手动将字符串放入标头+实现中,因为它不是您经常需要做的事情,因为我们正在讨论静态字符串常量(带有特殊语法糖的字符串文字,真的)。 – 2010-02-22 22:34:56

+0

@Roger Pate:我其实并没有想过使用数组模板,我也喜欢这个解决方案。您不会经常更改配置,以致难以更新N参数。 – 2010-02-23 14:47:31

9

原因很简单:在使用

extern const std::string FOO; 

您报头,并且

const std::string FOO("foo"); 

在适当.cpp文件。

+0

'approriate .cp file'是什么意思? – pm100 2010-02-22 20:15:29

+2

对于每个头文件(比如'foo.h'),通常都有一个合适的'.cpp'文件(在我们的例子中为'foo.cpp')。头文件可以被多次包含,所以它们通常包含数据声明(尽管通常是模板类的例外)。 '.cpp'文件包含实际的定义。 – Vlad 2010-02-22 20:24:56

2

在C++ 14,你可以做

const std::string FOO = "foo"s; 
+0

这并没有改变任何有关字符串初始化方式(或其中的位置或次数)的函数([ref](http://en.cppreference.com/w/cpp/string/basic_string/operator%22% 22)),所以我没有看到它与OP的实际问题有什么关系。 – 2017-02-11 13:09:17

0

我想你要寻找的是 '的boost ::轻量级<的std :: string>'

这是一个逻辑常量引用共享字符串值。非常有效的存储和高性能。

0

我的解决方案(具有能够使用时,这个问题以前回答说,不存在C++ 11种功能的优势):

#define INTERN(x) ([]() -> std::string const & { \ 
    static const std::string y = x; \ 
    return y; \ 
}()) 

my_map[INTERN("key")] = 5; 

是的,这是一个宏,它可以使用更好的名字。

相关问题