Is there a better way to copy the contents of a std::deque into a byte-array? It seems like there should be something in the STL for doing this.

// Generate byte-array to transmit
uint8_t * i2c_message = new uint8_t[_tx.size()];
if ( !i2c_message ) {
    errno = ENOMEM;
    ::perror("ERROR: FirmataI2c::endTransmission - Failed to allocate memory!");
} else {
    size_t i = 0;

    // Load byte-array
    for ( const auto & data_byte : _tx ) {
        i2c_message[i++] = data_byte;
    }

    // Transmit data
    _marshaller.sendSysex(firmata::I2C_REQUEST, _tx.size(), i2c_message);
    _stream.flush();

    delete[] i2c_message;
}

I'm looking for suggestions for either space or speed or both...

EDIT: It should be noted that _marshaller.sendSysex() cannot throw.

    
std::copy() maybe? – user0042 4 hours ago
    
I've only seen std::copy used to copy from array to container, not the opposite. – Zak 4 hours ago
    
First, i'd use a std::vector<uint8_t>. second, I'd use the iterator constructor to build said-same. That eliminates most of this code, including giving you reliable RAII. the data() member of the vector, or address-of-first-element, would grant you the pointer to contiguous data you seem to be seeking. – WhozCraig 4 hours ago
    
Supposed that std::deque is based on a std::vectoryou can use that equally well. – user0042 4 hours ago
1  
@user0042 std::deque is not mandated by the standard to be std::vector based. And it would make little sense that it would be, as the two container types excel at several tasks very differently. Both are random accessible containers, but deques are specialized for fast end (front or back) insertion and removal, while vectors offer guaranteed continuity; something not offered by a deque, and somewhat the point of why the OP is doing this. – WhozCraig 4 hours ago

1 Answer 1

Embrace the C++ standard library. Assuming _tx is really a std::deque<uint8_t>, one way to do this is simply:

std::vector<uint8_t> msg(_tx.cbegin(), _tx.cend());
_marshaller.sendSysex(firmata::I2C_REQUEST, msg.size(), msg.data());

This allocates the proper size contiguous buffer, copies the contents from the source iterator pair, and then invokes your send operation. The vector will be automatically cleaned up on scope-exit, and an exception will be thrown if the allocation for building it somehow fails.

I left out the seemingly pointless flush (for more than any other reason, because I couldn't see what was being flushed, nor why).

The standard library provides a plethora of ways to toss data around, especially given iterators that mark where to start, and where to stop. Might as well use that to your advantage. Additionally, letting RAII handle the ownership and cleanup of entities like this rather than manual memory management is nearly always a better approach, and should be encouraged.

In all, if you need continuity (and judging by the looks of that send-call, that's exactly why you're doing this), then copying from non-contiguous to contiguous space is pretty much your only option, and that takes space and copy-time. Not much you can do to avoid that. I suppose peeking into the implementation specifics of std::deque and possibly doing something like stacking send-calls would be possible, but I seriously doubt there would be any reward, and the only savings would likely evaporate in the multi-send invokes.

Finally, there is another option that may be worth considering. Look at the source of all of this. Is a std::deque really warranted? For example, certainly your building that container somewhere else. If you can do that build operation as-efficient, or nearly-so, using a std::vector, then this entire problem goes away, as you can just send that.

For example, if you knew (provably) that your std::deque would never be larger than some size N, you could, pre-size a std::vector or similar continuous RAII-protected allocation, to be 2*N in size, start both a fore and aft iterator pair in the middle and either prepend data by walking the fore iterator backward, or append data by walking the aft iterator forward. In the end, your data will be contiguous between fore and aft, and the send is all that remains. no copies would be required, though added-space is still required. This all hinges on knowing with certainty the maximum message size. If that is available to you, it may be an idea worth profiling.

1  
RAII is a great point. I didn't like the allocation, and that was what prompted me to ask the question. However, I know this is not more space efficient and I doubt it is more speed efficient, so while it is good advice, it doesn't really answer the question. – Zak 4 hours ago
    
@Zak if you need continuity (and judging by the looks of that send-call, that's exactly why you're doing this), then copying from non-contiguous to contiguous space is pretty much your only option, and that takes space and copy-time. Not much you can do to avoid that. I suppose peeking into the implementation specifics of std::deque and possibly doing something like stacking send-calls would be possible, but I seriously doubt there would be any reward, and the only savings would likely evaporate in the multi-send invokes. – WhozCraig 4 hours ago
    
Paste that into your answer and I'll give you the checkmark. Also thanks for the great, complete and well-thought out conversation. – Zak 4 hours ago
    
@Zak Done, along with an interesting mind-code you may find of use. Best of luck. – WhozCraig 4 hours ago