Подтвердить что ты не робот

Правильная идиома для констант 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: вынул вторичный вопрос, на который ответил 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) от vlad

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

c) от Roger P

 // really you cant do it

(b) кажется самым близким к тому, что я хотел, но имеет один фатальный недостаток. Я не могу иметь статический код уровня модуля, который использует эти строки, поскольку они, возможно, еще не были созданы. Я думал о (а) и фактически использовал подобный трюк при сериализации объекта, отправлял индекс, а не строку, но для решения общего назначения он представлял собой много сантехники. Так грустно (c) выигрывает, нет простой const idiom для std: string

4b9b3361

Ответ 1

Копирование и отсутствие "оптимизации строкового литерала" - это то, как работают std:: strings, и вы не можете получить именно то, что вы просите. Частично это объясняется тем, что виртуальные методы и dtor явно избегали. Интерфейс std::string в любом случае сложнее.

Стандарт требует определенного интерфейса как для std::string, так и для std:: map, и эти интерфейсы запрещают оптимизацию, которую вы хотели бы (как "непреднамеренные последствия" других своих требований, а не явно). По крайней мере, они запрещают это, если вы хотите фактически следовать всем подробным деталям стандарта. И вы действительно этого хотите, особенно когда так легко использовать другой строковый класс для этой конкретной оптимизации.

Однако этот отдельный класс строк может решить эти "проблемы" (как вы сказали, это редко проблема), но, к сожалению, в мире уже есть number_of_programmers + 1. Даже учитывая это переосмысление колес, я счел полезным иметь класс StaticString, который имеет подмножество интерфейса std::string: с использованием begin/end, substr, find и т.д. Он также запрещает модификацию (и вписывается в строковые литералы, которые путь), сохраняя только указатель char и размер. Вы должны быть немного осторожны, что он инициализируется только строковыми литералами или другими "статическими" данными, но это немного смягчается интерфейсом построения:

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 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.

Кроме того, вы также можете написать свой собственный класс карты, если это действительно общий сценарий для вас, и это может быть проще, чем изменение типов строк.

Ответ 2

Это просто: используйте

extern const std::string FOO;

в заголовке и

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

в соответствующем файле .cpp.

Ответ 3

  • Можно избежать накладных расходов при создании std::string, когда все, что вы хотите, является постоянной строкой. Но для этого вам нужно написать специальный класс, потому что в STL или Boost нет ничего подобного. Или лучшей альтернативой является использование класса, такого как StringPiece из Chromium или StringRef из LLVM. См. Этот связанный поток для получения дополнительной информации.

  • Если вы решите остаться с std::string (что вы, вероятно, захотите), то другой хороший вариант - использовать контейнер Boost MultiIndex, который имеет следующую функцию (цитируя документы):

    Boost MultiIndex [...] обеспечивает поиск операции, принимающие ключи поиска отличается от key_type индекс, который является особенно полезным объект, когда объекты key_type дорогой для создания.

Карты с дорогостоящими ключами от Andrei Alexandrescu (Журнал пользователей C/С++, февраль 2006 г.) связан с вашей проблемой и очень хорошо читайте.

Ответ 4

В С++ 14 вы можете сделать

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

Ответ 5

Правильная идиома - это та, которую вы используете. 99,99% времени не нужно беспокоиться об издержках конструктора std::string.

Я действительно задаюсь вопросом, может ли конструктор std::string быть превращен в внутреннюю функцию компилятором? Теоретически это возможно, но мой комментарий выше был бы объяснением, достаточным для того, почему этого не произошло.

Ответ 6

Похоже, вы уже знаете, что будут во время выполнения строковыми литералами, поэтому вы можете настроить внутреннее сопоставление между перечисленными значениями и массивом строк. Затем вы должны использовать перечисление вместо фактического литерала 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"];

Ответ 7

Проблема заключается в том, что std::map копирует ключ и значения в свои собственные структуры.

У вас может быть std::map<const char *, const char *>, но вам нужно будет предоставить функциональные объекты (или функции) для сравнения данных ключа и значения, поскольку этот трафарет предназначен для указателей. По умолчанию map будет сравнивать указатели, а не данные, на которые указывают указатели.

Компромисс - это разовая копия (std::string) по сравнению с доступом к компаратору (const char *).

Другой вариант - написать собственную функцию map.

Ответ 8

Я думаю, что вы ищете "boost:: flyweight < std::string > '

это логически константная ссылка на общее строковое значение. очень эффективное хранение и высокая производительность.

Ответ 9

Мое решение (имеющее то преимущество, что имело возможность использовать возможности С++ 11, которых не было, когда этот вопрос был ранее ответил):

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

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

Да, это макрос, и он может использовать лучшее имя.