简体   繁体   中英

Learning C++ vectors… Am I doing this correctly? Can my repeated calls to vector::push_back() be simplified?

First let me say this program is in no way meant to be useful. This is just me writing something to test and learn how to use vectors.

In this example, I am employing several actions that I would need to do in a real life project I'm working on. I had to figure out how to do a lot of this from about 20 different examples found on Google.

This is the first time I've ever done anything with vectors and I want to make sure that everything I'm doing is safe . I also want to know if my repeated calls to vector::push_back() could be simplified? Please note that the code does work as I am intending it to. I am just looking to see if I'm overlooking anything, misunderstanding anything, making unsafe calls, etc.

Last thing... I'm also wanting to learn how to use smart pointers ( auto_ptr ). But so far, I don't see that it would benefit this example?

#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;

}


Note: Please do **not** mention boost, MFC, or ATL.

You can simplify initializing msg with:

#include <algorithm>

message msg(10);
std::copy_n("\x00\x00\x01\x82\x00\x06\x01\x01\x20\x01\x25\x04", msg, 10);

I also want to know if my repeated calls to vector::push_back() could be simplified?

Not much. In C++11, you can initialize the vector with the list of elements ( message msg = {0x00, 0x00, 0x01.....} , but that requires the compiler to support this feature, and it's quite likely that your compiler doesn't yet support it.

Last thing... I'm also wanting to learn how to use smart pointers (auto_ptr). But so far, I don't see that it would benefit this example?

You're right. The magic word is RAII: Resource Acquisition is Initialization. What it means is that resources should be owned by C++ objects, which take care of acquiring and releasing the resources. A smart pointer is one way to do this (you have a pointer to some heap-allocated memory, and you wrap it in a smart pointer, which takes care of releasing the memory when it is time), but std::vector (or the other standard library containers also implement RAII semantics. They own the objects placed in them, and they take care of releasing them when they need to be released.

Your own home-built objects can also do the same. Smart pointers are nothing special in this respect, they're just a convenient shortcut for RAII'ifying raw pointers. But there are many other ways to make your resources use RAII.

(One of my pet peeves is that many people seem to think that smart pointers (and specifically shared_ptr ) are all that RAII is about: that if you want to avoid memory leaks, you need to put everything in a shared_ptr . That's not true. All that matters is that your objects are designed with clear ownership semantics, so that it is always clear which object owns a resource (a resource might be a memory allocation, a file handle, a window, a network socket or anything else that needs to be acquired and released), and so that the owner will take care of releasing the owned resource. Smart pointers are just premade classes that behave like this.

About auto_ptr , stay far away from it. It's not very useful. It can not be safely stored in the std containers, and it has some quirky behavior, which is why it is deprecated in C++11.

Instead, use the "new" generation of smart pointers: shared_ptr (if you want shared ownership), scoped_ptr (if you want exclusive, non-transferable ownership tied to a single scope), or unique_ptr for something that behaves like an auto_ptr but which works .

The first two are in Boost (which you should use in any case) and TR1 (which probably comes with your compiler), and all three are in the C++11 standard library.

Another way to simplify:

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;

Here is a better way to initialize your message:

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])));

In C++11 you could, of course, do this:

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

As for the swap trick mentioned elsewhere... I don't think it's a good idea. Unless your buffer sometimes has absolutely huge messages and then goes back to having relatively small messages you lose more than you gain with the swap trick. You don't want to have the underlying vector be allocating memory all the time, and swapping with a new empty vector forces exactly that to happen.

BTW, the constructor call can be replaced with a call to assign when you re-use the vector, like so:

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

Also, there is a problem with your code. You are not verifying that the file size will fit into a 16 bit value. This means if you try to send a file that's too big for the size field in your message you will have a broken size field. This is particularly vexing since you send the entire file regardless of what's in the size field. This means the contents of the file may be interpreted as the header for the next message in some circumstances.

Fortunately (though I expect rather accidentally) your use of tellg , resize , and read do conspire to ensure that you will crash from an out-of-memory exception rather than have a buffer overrun if your file is too big to fit into an in-memory buffer. Though there is the possibility of negative underrun if your file size is greater than 2G. I won't hazard a guess as to what wil happen if that occurs. length should probably not be an integer. Figure out what type tellg is supposed to give you and use that instead.

Yes they could be with boost libraries assign ( boost assign ) extras or in C++11 they made it smarter too.

Overall you do not need to mix reserve and resize. You should choose which works best for you. reserve() guarantees that you can allocate that many items without the vector reallocating space. resize() means the memory is already allocated and you are allowed to write to it.

Also remember by the standard a vector is never required to give back the memory it has allocated. So for example if your vector grows to a million elements and there after you only need 10 - the vector shall hold system memory for the ability to store million elements. This can be fixed by the swap() trick

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM