Какую форму лучше использовать для карты

#c

Вопрос:

Дан класс Ac и карта:

 std::map<std::string, Ac> ma;
 

Какая из следующих форм лучше:

 ma["112"] = Ac(); // (1)
 

или

 Ac ac;
ma["112"] = ac;   // (2)
 

Есть ли какие-либо проблемы с использованием версии 1?

Фактический код в контексте:

 struct Ac
{
    bool first;
    bool second;
};

int main()
{   
    std::map<std::string, Ac> ma;
    std::string st = "10.12";
    if (ma.find(st) == ma.end())
    {   
#if 0
        // 1
        ma["112"] = Ac();
#else            
        // 2
        Ac ac;
        ma["112"] = ac;
#endif
    }
    else
        ma[st].first = true;

    return 0;
}
 

Комментарии:

1. Они не выражают одно и то же. в #2 ac все еще существует и может использоваться после установки его копии на карте.

2. это зависит от того, что вам нужно и что вы хотите сделать. Вы также можете вставить элемент через ma["112"]; . Рассмотрите insert или emplace когда фактическая цель состоит в том, чтобы вставить элемент в карту.

3. моя цель-вставить в карту и использовать после этого только структуру из карты.

4. В таком простом случае компилятор, скорее всего, предпочтет, чтобы вторая версия работала так же, как и первая. единственная проблема, которую я вижу в обеих версиях, заключается в том, что переменные-члены объекта неинициализированы.

5. В этом случае вам следует использовать std::map::emplace , который выполняет все это сразу. (правка, заметил тег c 14, поэтому рекомендовал совместимый метод)

Ответ №1:

Я полагаю, вы не хотите оставлять участников неинициализированными, поэтому давайте предоставим инициализаторы по умолчанию:

 struct Ac
{
    bool first = false;
    bool second = false;
};
 

Далее, вы слишком часто ищете ключ. Сначала вы вызываете find , и когда элемент был найден, вы вызываете operator[] , который должен найти элемент снова. Вместо этого используйте итератор, возвращенный из find .

Что касается вашего вопроса, наиболее очевидная разница заключается в том, что одна версия добавляет копию временной, в то время как другая вставляет копию локальной переменной. Если вам не нужна локальная переменная, вы можете вызвать emplace , чтобы построить элемент на месте:

 int main()
{   
    std::map<std::string, Ac> ma;
    std::string st = "10.12";
    auto ret = ma.emplace(st,Ac{});
    if (! ret.second)
    {   
        ret.first->second.first = true;
    }
}
 

emplace (аналогично insert ) возвращает pair bool значение, указывающее, имела ли место вставка, и итератор для элемента. ret.first->second.first это может немного сбить с толку. ret.first является ли итератор элемента на карте ret.first->second Ac частью этого элемента и ret.first->second.first является его first членом.

После оптимизации компилятора это, вероятно, так же эффективно, как и любая из ваших двух версий, но я считаю, что его более удобно использовать emplace , когда вы хотите вставить элемент, и поскольку построение an Ac сравнительно дешево, также не так уж плохо сконструировать временную так или иначе, не проверяя карту раньше. Чтобы быть действительно уверенным в том, что работает лучше, вам нужно будет измерить это. В общем, std::unordered_map можно ожидать, что он будет быстрее, потому что его элементы не сортируются по ключам.

Комментарии:

1. спасибо за ваш ответ, если ma.emplace(st,Ac{}); используется со списком инициализаторов по умолчанию, все равно необходимо добавить bool first = false; значения обычно для членов структуры, не будет ли это сделано списком инициализаторов по умолчанию при вставке в карту ?

2. @BenRice что такое «список инициализаторов по умолчанию»? Извините, я не понимаю, что вы имеете в виду. В вашем коде элементы не инициализируются, если элемент еще не был на карте раньше

3. если я просто вставлю в карту только тогда,когда элемент для данного ключа не был найден с помощью ma.emplace(st, Ac{}); этого будет недостаточно ? Я имею в виду, что таким образом элементы переменного тока будут инициализированы

4. @BenRice из вашего комментария («могу ли я использовать список инициализаторов по умолчанию для false при вставке нового элемента в карту ?») Я предположил, что для вновь созданного элемента должен быть установлен элемент false . Если вы хотите true в обоих случаях, то emplace достаточно только одного. Хотя в вашем коде члены Ac не инициализируются (также нет, если вы используете emplace(st,Ac{}) )

5. Требуются два поиска, существуют разные ключи, используемые для find и []

Ответ №2:

В (2) объект ac существует до конца области, в то время как (1) создает временное, которое немедленно отбрасывается, когда оно больше не требуется.

Он также не содержит символа или ссылки, с помощью которых вы могли бы получить доступ к временному; вы можете получить доступ только к объекту, созданному в map самом объекте, который является вашей заявленной целью.

Таким образом, на самом деле речь идет не о «лучшем«, а о том, что наиболее уместно в конкретной ситуации, поскольку они не являются семантически эквивалентными. В этом случае (1), однако, если вы хотите создать объект, выполните над ним некоторые операции, прежде чем поместить его на карту, потребуется (2).

Если вы выполнили над ним операции после копирования на карту, то ясно, что эти изменения будут касаться объекта, а не копии на карте.

Комментарии:

1. Спасибо @Clifford