Изучение векторов C ++ ... Я делаю это правильно?Могут ли мои повторные вызовы vector :: push_back () быть упрощены? - PullRequest
0 голосов
/ 03 декабря 2011

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

В этом примере я использую несколько действий, которые мне нужно будет выполнить в реальном проекте, над которым я работаю.Мне пришлось выяснить, как это сделать, из примерно 20 различных примеров, найденных в Google.

Это первый раз, когда я что-то делал с векторами, и Я хочу убедиться, что все, что я делаю, безопасно . Я также хочу знать, могут ли быть упрощены мои повторные вызовы на vector::push_back()? Обратите внимание, что код работает так, как я намереваюсь.Я просто смотрю, не пропускаю ли я что-либо, неправильно что-то понимаю, делаю небезопасные вызовы и т. Д.

Последнее ... Я также хочу научиться использовать умные указатели (auto_ptr).Но пока я не вижу, что это принесло бы пользу этому примеру?

#include <iostream>
#include <fstream>
#include <vector>

using namespace std;
typedef vector<unsigned char> message;


void append_file( message& buffer, int header_size )
{

    ifstream is("vector",ifstream::binary);
    is.seekg(0,ios::end);
    int length = is.tellg();
    is.seekg(0, ios::beg);

    buffer.reserve( header_size + length );
    buffer.resize( header_size +length );

    is.read( (char*)&buffer[header_size], length );
    is.close();

    buffer.at(4) = (buffer.size() & 0xFF00) >> 8;
    buffer.at(5) = buffer.size() & 0xFF;

}

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

    while( true ) //this is just for me to watch memory usage
    {
        message msg;

        msg.push_back(0x00);
        msg.push_back(0x00);
        msg.push_back(0x01);
        msg.push_back(0x82);
        msg.push_back(0x00);
        msg.push_back(0x06);

        msg.push_back(0x01);
        msg.push_back(0x01);
        msg.push_back(0x20);
        msg.push_back(0x01);
        msg.push_back(0x25);
        msg.push_back(0x04);

        int header = msg.size();

        append_file( msg, header );

        ofstream os("test.dt",ofstream::binary);
        os.write( (char*)&msg[0], msg.size() );
        os.close();

        cout << "Wrote " << msg.size() << " bytes" << endl;

        cout << "Press any key to repeat...";
        cin.get();

    } //it is my understanding from my reading on vectors that the memory will be de-allocated here. Is this correct? It seems to be? At least on Linux.

    return 0;

}

Примечание: пожалуйста, ** не ** упоминайте надстройку, MFC или ATL.

Ответы [ 5 ]

1 голос
/ 03 декабря 2011

Вот лучший способ инициализировать ваше сообщение:

static const char header[] = { '\x00', '\x00', '\x01', '\x82', '\x00',
                               '\x06', '\x01', '\x01', '\x20', '\x01',
                               '\x25', '\x04' };
vector<char> msg(header, header + (sizeof(header) / sizeof(header[0])));

В C ++ 11 вы могли бы, конечно, сделать это:

vector<char> msg { '\x00', '\x00', '\x01', '\x82', '\x00', '\x06',
                   '\x01', '\x01', '\x20', '\x01', '\x25', '\x04' };

Что касается уловки swap, упомянутой в другом месте ... Я не думаю, что это хорошая идея. Если в вашем буфере иногда нет абсолютно огромных сообщений, а затем вы возвращаетесь к относительно маленьким сообщениям, вы теряете больше, чем вы получаете с помощью трюка swap. Вы не хотите, чтобы нижележащий вектор постоянно выделял память, а замена новым пустым вектором заставляет в точности это произойти.

Кстати, вызов конструктора можно заменить вызовом assign при повторном использовании вектора, например так:

 msg.assign(header, header + (sizeof(header) / sizeof(header[0]));

Также существует проблема с вашим кодом. Вы не проверяете, что размер файла будет соответствовать 16-битному значению. Это означает, что если вы попытаетесь отправить файл, который слишком велик для поля размера в вашем сообщении, у вас будет неправильное поле размера. Это особенно неприятно, поскольку вы отправляете весь файл независимо от того, что находится в поле размера. Это означает, что в некоторых случаях содержимое файла может интерпретироваться как заголовок для следующего сообщения.

К счастью (хотя я ожидаю довольно случайно), использование tellg, resize и read сговорились, чтобы гарантировать, что вы будете аварийно завершать работу из-за нехватки памяти, а не переполнять буфер, если ваш Файл слишком большой, чтобы поместиться в буфер в памяти. Хотя есть вероятность отрицательного опустошения, если размер вашего файла превышает 2G. Я не рискну догадываться, что произойдет, если это произойдет. length, вероятно, не должно быть целым числом. Выясните, какой тип tellg должен вам дать, и используйте его вместо этого.

1 голос
/ 03 декабря 2011

Еще один способ упростить:

message m;
std::back_insert_iterator<message> i(m);

i = 0x00;
i = 0x00;
i = 0x01;
i = 0x82;
i = 0x00;
i = 0x06;

i = 0x01;
i = 0x01;
i = 0x20;
i = 0x01;
i = 0x25;
i = 0x04;
1 голос
/ 03 декабря 2011

Вы можете упростить инициализацию msg с помощью:

#include <algorithm>

message msg(10);
std::copy_n("\x00\x00\x01\x82\x00\x06\x01\x01\x20\x01\x25\x04", msg, 10);
1 голос
/ 03 декабря 2011

Я также хочу знать, могут ли быть упрощены мои повторные вызовы vector :: push_back ()?

Не очень.В C ++ 11 вы можете инициализировать вектор списком элементов (message msg = {0x00, 0x00, 0x01.....}, но для этого требуется, чтобы компилятор поддерживал эту функцию, и вполне вероятно, что ваш компилятор пока не поддерживает его.

И последнее ... Я также хочу научиться использовать умные указатели (auto_ptr). Но до сих пор я не вижу, что это принесло бы пользу этому примеру?

Вы правы. Волшебное слово RAII: получение ресурсов - инициализация. Это означает, что ресурсы должны принадлежать объектам C ++, которые заботятся о получении и освобождении ресурсов. Одним из способов сделать это является умный указатель (выу вас есть указатель на некоторую выделенную кучу память, и вы оборачиваете его в умный указатель, который заботится об освобождении памяти, когда это время), но std::vector (или другие контейнеры стандартной библиотеки также реализуют семантику RAII.объекты, помещенные в них, и они позаботятся об их освобождении, когда они должны быть освобождены.

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

(Одна из моих любимых мыслей в том, что многие люди думают, что умные указатели (и в особенности shared_ptr) - это все, о чем RAII говорит:если вы хотите избежать утечек памяти, вам нужно поместить все в shared_ptr. Это не так. Все, что имеет значение, это то, что ваши объекты спроектированы с четкой семантикой владения, так что всегда ясно, какиеобъект владеет ресурсом (ресурс может быть выделением памяти, дескриптором файла, окном, сетевым сокетом или чем-либо еще, что необходимо получить и освободить), и поэтому владелец позаботится об освобождении принадлежащего ему ресурса. Smartуказатели - это просто готовые классы, которые ведут себя следующим образом.

О auto_ptr, держитесь подальше от него. Это не очень полезно. Его нельзя безопасно хранить в контейнерах std, и оно имеет некоторые причудливыеповедение, поэтому оно устарело в C ++ 11.

Вместо этого используйте «новое» поколение интеллектуальных указателей: shared_ptr (iесли вам нужно совместное владение), scoped_ptr (если вы хотите, чтобы исключительное непередаваемое владение было привязано к одной области), или unique_ptr для чего-то, что ведет себя как auto_ptr, но которое работает .

Первые два находятся в Boost (который вы должны использовать в любом случае) и TR1 (который, вероятно, поставляется с вашим компилятором), и все три находятся в стандартной библиотеке C ++ 11.

0 голосов
/ 03 декабря 2011

Да, они могут быть с дополнительными библиотеками boost ( boost assign ) или в C ++ 11 они тоже сделали это умнее.

В целом вам не нужно смешивать резерв и изменять размер. Вы должны выбрать, что работает лучше для вас. Reserve () гарантирует, что вы можете выделить столько элементов без перераспределения пространства для вектора. resize () означает, что память уже выделена, и вам разрешено писать в нее.

Также помните, что по стандарту вектор никогда не обязан возвращать выделенную память. Так, например, если ваш вектор увеличивается до миллиона элементов, а после него вам нужно только 10 - вектор должен содержать системную память для возможности хранения миллиона элементов. Это можно исправить с помощью трюка swap ()

std::vector<int> foo(100000);

std::vector<int> small(10);

foo.swap(small); // now when small goes out of scope the 1,000,000 mem allocated
                 // vector shall be freed;
...