2010-10-29 5 views
2

Я видел, как мой коллега часто делал второй фрагмент. Почему это? Я пробовал добавлять операторы печати для отслеживания ctors и dtors, но оба кажутся одинаковыми.Эффективный push_back классов и структур

std::vector<ClassTest> vecClass1; 
    ClassTest ct1; 
    ct1.blah = blah // set some stuff 
    ... 
    vecClass1.push_back(ct1); 

std::vector<ClassTest> vecClass2; 
    vecClass2.push_back(ClassTest()); 
    ClassTest& ct2 = vecClass2.back(); 
    ct2.blah = blah // set some stuff 
    ... 

PS. Извините, если заголовок вводит в заблуждение.

Edit:

Во-первых, спасибо всем за ваши ответы.

Я написал небольшое приложение, используя std::move. Результаты удивляют меня, возможно, потому, что я сделал что-то неправильно ... кто-то, пожалуйста, объяснит, почему «быстрый» путь работает значительно лучше.

#include <vector> 
#include <string> 
#include <boost/progress.hpp> 
#include <iostream> 

const std::size_t SIZE = 10*100*100*100; 
//const std::size_t SIZE = 1; 
const bool log = (SIZE == 1); 

struct SomeType { 
    std::string who; 
    std::string bio; 
    SomeType() { 
     if (log) std::cout << "SomeType()" << std::endl; 
    } 
    SomeType(const SomeType& other) { 
     if (log) std::cout << "SomeType(const SomeType&)" << std::endl; 
     //this->who.swap(other.who); 
     //this->bio.swap(other.bio); 
     this->who = other.who; 
     this->bio = other.bio; 
    } 
    SomeType& operator=(SomeType& other) { 
     if (log) std::cout << "SomeType::operator=()" << std::endl; 
     this->who.swap(other.who); 
     this->bio.swap(other.bio); 
     return *this; 
    } 
    ~SomeType() { 
     if (log) std::cout << "~SomeType()" << std::endl; 
    } 
    void swap(SomeType& other) { 
     if (log) std::cout << "Swapping" << std::endl; 
     this->who.swap(other.who); 
     this->bio.swap(other.bio); 
    } 
     // move semantics 
    SomeType(SomeType&& other) : 
      who(std::move(other.who)) 
     , bio(std::move(other.bio)) { 
     if (log) std::cout << "SomeType(SomeType&&)" << std::endl; 
    } 
    SomeType& operator=(SomeType&& other) { 
     if (log) std::cout << "SomeType::operator=(SomeType&&)" << std::endl; 
     this->who = std::move(other.who); 
     this->bio = std::move(other.bio); 
     return *this; 
    } 
}; 

int main(int argc, char** argv) { 

    { 
     boost::progress_timer time_taken; 
     std::vector<SomeType> store; 
     std::cout << "Timing \"slow\" path" << std::endl; 
     for (std::size_t i = 0; i < SIZE; ++i) { 
      SomeType some; 
      some.who = "bruce banner the hulk"; 
      some.bio = "you do not want to see me angry"; 
      //store.push_back(SomeType()); 
      //store.back().swap(some); 
      store.push_back(std::move(some)); 
     } 
    } 
    { 
     boost::progress_timer time_taken; 
     std::vector<SomeType> store; 
     std::cout << "Timing \"fast\" path" << std::endl; 
     for (std::size_t i = 0; i < SIZE; ++i) { 
      store.push_back(SomeType()); 
      SomeType& some = store.back(); 
      some.who = "bruce banner the hulk"; 
      some.bio = "you do not want to see me angry"; 
     } 
    } 
    return 0; 
} 

Выход:

[email protected]:~/Desktop/perf_test$ g++ -Wall -O3 push_back-test.cpp -std=c++0x 
[email protected]:~/Desktop/perf_test$ ./a.out 
Timing "slow" path 
3.36 s 

Timing "fast" path 
3.08 s 
+2

Преждевременная оптимизация, я думаю. – kennytm

+1

Обратите внимание, что в C++ «классы и структуры» избыточны, поскольку классы и структуры - это одно и то же. –

+1

Относительно вашего редактирования. Сначала нужно проверить, имеет ли ваша реализация эффективный 'move' для' std :: string', и ваша реализация имеет 'std :: vector :: push_back (T &&)'. Если ответ на «нет», то код 'std :: move' выполняет копию (потому что поддержка C++ 11 для вашей реализации является неполной). –

ответ

3

Если мы принимаем, что фрагмент вашего коллеги мудр, потому что ClassTest дорого копировать, я предпочел бы:

using std::swap; 

std::vector<ClassTest> vecClass1; 
ClassTest ct1; 
ct1.blah = blah // set some stuff 
... 
vecClass1.push_back(ClassTest()); 
swap(ct1, vecClass1.back()); 

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

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

Как говорит Джин, std::move лучше, если у вас есть функция C++ 0x.

Если мы беспокоимся о том, что ClassTest стоит дорого копировать, то перенос вектора является ужасающей перспективой. Таким образом, мы также должны либо:

  • резерва достаточно места, прежде чем добавить что-нибудь,
  • использовать deque вместо vector.
7

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

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

+0

Mmm ... это * может * быть, но если объект дорог для копирования, зачем сохранять объекты вместо указателей (или 'shared_ptr', если на то пошло)? –

+0

@ Diego: Некоторые люди делают самые сумасшедшие вещи во имя «производительности» (критические цитаты важны) –

+0

ха-ха. Ты в порядке! –

0

Они идентичны (насколько я могу видеть). Возможно, он или она делает это как идиоматический обычай.

1

Возможно, вам стоит задать вопрос : Почему, но мы все еще можем предположить. Как отметил Джеймс, это может быть чуть более эффективным, если объект будет стоить дороже для копирования после его создания.

Я вижу преимущества в обеих версиях.

Мне нравится ваш отрывок из колледжа, потому что: хотя в обоих случаях есть 2 объекта, они только сосуществуют в течение очень короткого периода времени во второй версии. Для редактирования доступен только один объект: это позволяет избежать возможной ошибки редактирования ct1 после push_back.

Мне нравится ваш личный фрагмент кода, потому что: применение push_back добавить второй объект потенциально аннулирует ссылку ct2, вызывая риск неопределенного поведения. Первый фрагмент этого риска не представляет.

2

Вторая версия дает возможность перемещать временное. Первая версия - это копирование временного вектора. Второй - потенциально быстрее. Вторая версия также имеет потенциально меньшие потребности в пиковой памяти, первая версия создает два объекта одну временную и одну копию, а затем удаляет временную.Вы можете улучшить первую версию, явно перемещая временное:

std::vector<ClassTest> vecClass1; 
ClassTest ct1; 
ct1.blah = blah // set some stuff 
... 
vecClass1.push_back(std::move(ct1));