【问题标题】:correct idiom for std::string constants?std::string 常量的正确习语?
【发布时间】:2011-01-19 17:59:54
【问题描述】:

我有一张代表数据库对象的地图。我想从中获得“众所周知”的价值

 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) 来自马克 b

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

b) 来自弗拉德

 // 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 没有 simple const idiom

【问题讨论】:

  • 重新编辑,我确实尝试解决这个问题(“不,这不是 std::string 的工作方式”和“这是另一个字符串类”),但请注意,如果你只是想存储 const std::strings 在某个地方作为全局变量,您只需为分配一次(在程序启动时)使用 ref-counted 实现(gcc 提供并且标准明确允许)。只需确保这些全局变量是 const 即可轻松避免各种逻辑错误。
  • 所以你的回答是——不可能。不清楚。如果没有人想出更好的答案,那么你会得到对勾
  • 其实我目前很喜欢曼努埃尔的回答。抱歉我不够清楚,这就是我发表评论的原因,但我也无意中偷偷加入了另一个选择。 :)
  • 但他仍然没有说如何做 std::string 常量。

标签: c++ string constants


【解决方案1】:

“字符串文字优化”的复制和缺乏正是 std::strings 的工作方式,您无法准确获得所需的内容。部分原因是因为明确避免了虚拟方法和 dtor。无论如何,std::string 接口很多很复杂。

该标准要求 std::string 和 std::map 都有特定的接口,而这些接口碰巧不允许您进行您想要的优化(作为其其他要求的“意外后果”,而不是明确地)。至少,如果你想真正遵循标准的所有细节,他们不允许这样做。而且您确实想要这样,尤其是当使用不同的字符串类进行此特定优化非常容易时。

但是,单独的字符串类可以解决这些“问题”(正如您所说,这很少成为问题),但不幸的是,世界上已经有 number_of_programmers + 1 了。即使考虑到轮子的重新发明,我发现拥有一个具有 std::string 接口子集的 StaticString 类很有用:使用 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++ 中的字符串文字。

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

【讨论】:

  • 如果有足够的需求,我可以从头开始重新发明这个类,以便在 SO 的开源许可下使用 SO(我通常对此非常小心)。假设如果这条评论获得 +10 票,那么我会这样做。 :)
  • @RogerPate 你现在上钩了
【解决方案2】:

很简单:使用

extern const std::string FOO;

在您的标题中,并且

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

在相应的.cpp 文件中。

【讨论】:

  • “适当的 .cp 文件”是什么意思?
  • 对于每个标题(例如,foo.h),通常都有一个适当的.cpp 文件(在我们的例子中为foo.cpp)。标头可以包含多次,因此它们通常包含数据声明(但通常模板类除外)。 .cpp 文件包含实际定义。
【解决方案3】:
  1. 当您想要的只是一个常量字符串时,可以避免创建std::string 的开销。但是您需要为此编写一个特殊的类,因为在 STL 或 Boost 中没有类似的东西。或者更好的选择是使用像来自 Chromium 的 StringPiece 或来自 LLVM 的 StringRef 这样的类。请参阅此related thread 了解更多信息。

  2. 如果您决定继续使用 std::string(您可能会这样做),那么另一个不错的选择是使用 Boost MultiIndex 容器,它具有以下功能(引用 the docs):

    Boost MultiIndex [...] 提供查找 接受搜索键的操作 不同于 key_type 的 索引,这是一个特别有用的 当 key_type 对象是 创建成本高。

Maps with Expensive Keys 由 Andrei Alexandrescu 撰写(C/C++ 用户日志,2006 年 2 月)与您的问题相关,值得一读。

【讨论】:

  • @Manuel 你是对的 - pdf 是完全匹配且有趣的阅读。他没有讨论这个永恒的问题
【解决方案4】:

正确的成语是您正在使用的成语。 99.99% 的时间不需要担心 std::string 的构造函数的开销。

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

【讨论】:

  • 更好的是,只需让优化器足够先进,以弄清楚如何在字符串的 ctor 中使用 strlen(代码归结为 std::string 的代码,或 wstring 的 wcslen ) 一个内在的,当用于字符串文字时。更难,更真实,我不知道是否有人这样做,但它的好处不仅仅是 std::string。 ——哦,我傻了,这只是成功的一半,因为你还必须复制数据。嗯。
  • 是的,问题在于认识到构造函数正在传递一个静态常量,并在不复制的情况下设置内部存储指针。标准类型信息没有达到那种详细程度,因此没有特殊的编译器魔法是不可能的。
【解决方案5】:

您似乎已经知道在运行时字符串字面量是什么,因此您可以在枚举值和字符串数组之间建立一个内部映射。然后,您将在代码中使用枚举而不是实际的 const char* 文字。

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

【讨论】:

  • @pm100:您可以使用各种代码生成技巧来提前“了解”数据——类似于 gettext i18n 库提取字符串的方式;但是,这是很多工作,您可以根据需要简单地手动将字符串放入标题+实现中,因为这不是您应该经常做的事情,因为我们正在谈论静态字符串常量(带有特殊语法糖的字符串文字,真的)。
  • @Roger Pate:我实际上没有考虑过使用数组模板,我也很喜欢这个解决方案。您更改配置的频率不足以让更新 N 参数变得痛苦。
【解决方案6】:

在 C++14 中你可以这样做

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

【讨论】:

  • 这不会改变关于如何(或在哪里,或多少次)初始化字符串(ref)的任何功能,所以我看不出它与 OP 的实际问题有什么关系。
【解决方案7】:

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

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

权衡是一次性复制 (std::string) 与访问比较器 (const char *)。

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

【讨论】:

  • 是第一行,当然现在还不错,因为我们有移动语义。
【解决方案8】:

我认为你正在寻找的是'boost::flyweight '

这是对共享字符串值的逻辑 const 引用。非常高效的存储和高性能。

【讨论】:

    【解决方案9】:

    我的解决方案(具有能够使用之前回答此问题时不存在的 C++11 功能的优势):

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-03
      • 2013-05-28
      • 2013-10-08
      • 2020-04-12
      • 2013-09-12
      • 2021-07-12
      相关资源
      最近更新 更多