правильная идиома для констант std :: string? - PullRequest
24 голосов
/ 22 февраля 2010

У меня есть карта, которая представляет объект БД. Я хочу получить из него «общеизвестные» значения

 std::map<std::string, std::string> dbo;
 ...
 std::string val = map["foo"];

все хорошо, но мне кажется, что "foo" конвертируется во временную строку при каждом вызове. Конечно, было бы лучше иметь постоянную std :: string (конечно, это, вероятно, крошечные издержки по сравнению с дисковым вводом-выводом, который только что извлек объект, но я думаю, что это все еще правильный вопрос). Итак, какова правильная идиома для констант std :: string?

например - я могу иметь

 const std::string FOO = "foo";

в HDR, но затем я получаю несколько копий

РЕДАКТИРОВАТЬ: Ответ еще не сказал, как объявить константы std :: string. Игнорировать всю карту, STL и т. Д. Большая часть кода сильно ориентирована на std :: string (мой, конечно, есть), и естественно, что им нужны константы, не платя снова и снова за выделение памяти

EDIT2: вынул второстепенный вопрос, на который ответил PDF от Мануэля, добавил пример плохой идиомы

РЕДАКТИРОВАТЬ3: Сводка ответов. Обратите внимание, что я не включил те, которые предложили создать новый класс строки. Я разочарован, потому что я надеялся, что есть простая вещь, которая будет работать только в заголовочном файле (например, const char * const). Во всяком случае

а) от Марка Б

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

б) от влад

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

в) от Roger P

 // really you cant do it

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

Ответы [ 9 ]

17 голосов
/ 22 февраля 2010

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

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

Тем не менее, этот отдельный строковый класс может решить эти "проблемы" (как вы сказали, это редко бывает проблемой), но, к сожалению, в мире уже есть number_of_programmers + 1 из них. Даже принимая во внимание это изобретение колеса, я нашел полезным иметь класс StaticString, который имеет подмножество интерфейса std :: string: использование 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.

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

9 голосов
/ 22 февраля 2010

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

extern const std::string FOO;

в заголовке и

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

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

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

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

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

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

1 голос
/ 27 декабря 2014

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

const std::string FOO = "foo"s;
1 голос
/ 22 февраля 2010

Похоже, вы уже знаете, какими будут строковые литералы во время выполнения, поэтому вы можете настроить внутреннее отображение между перечисляемыми значениями и массивом строк. Тогда вы будете использовать перечисление вместо фактического константа 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 std::string& val = map["foo"];
1 голос
/ 22 февраля 2010

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

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

0 голосов
/ 29 декабря 2017

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

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

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

Да, это макрос, и он может использовать более подходящее имя.

0 голосов
/ 27 декабря 2014

Я думаю, вы ищете 'boost :: flyweight '

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

0 голосов
/ 22 февраля 2010

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

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

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

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

...