Эффективный push_back классов и структур - PullRequest
2 голосов
/ 29 октября 2010

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

    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.Извините, если название вводит в заблуждение.

Редактировать:

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

Я написалнебольшое приложение, использующее 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;
}

Вывод:

dev@ubuntu-10:~/Desktop/perf_test$ g++ -Wall -O3 push_back-test.cpp -std=c++0x
dev@ubuntu-10:~/Desktop/perf_test$ ./a.out 
Timing "slow" path
3.36 s

Timing "fast" path
3.08 s

Ответы [ 5 ]

7 голосов
/ 29 октября 2010

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

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

3 голосов
/ 29 октября 2010

Если мы примем, что фрагмент вашего коллеги является мудрым, потому что 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.
2 голосов
/ 29 октября 2010

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

std::vector<ClassTest> vecClass1;
ClassTest ct1;
ct1.blah = blah // set some stuff
...
vecClass1.push_back(std::move(ct1));
1 голос
/ 29 октября 2010

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

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

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

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

0 голосов
/ 29 октября 2010

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

...