1

My server gets some requests, compiles response and sends it back. Sometimes response is a file, sometimes it’s HTML. So I define response in the following way:

class ResponseData
{

public:
    bool operator== (const ResponseData & param) const { return id == param.id; }

public:
    RequestIdType id;
    RequestType type;
    std::vector<char> data;
};

When I compile HTML response I use std::stringstream.

std::vector<char> RequestHandler::createResponse( const RequestData * request ) const
{
    std::stringstream buffer;

    std::vector<char> result;
    result.assign( RESPONSE_HEADER, RESPONSE_HEADER + strlen( RESPONSE_HEADER ) );

    buffer << "<tr>";


    for( auto param : request->paramsMap )
    {
        buffer << "<tr><td>" << param.first << "</td><td>" << param.second << "</td></tr>\n";
    }

    buffer << "<\tr>";

    DEBUG_LOG_F << buffer.str();

    std::string str = buffer.str();

    result.insert( result.end(), str.begin(), str.end() );
    result.insert( result.end(), RESPONSE_FOOTER, RESPONSE_FOOTER + strlen( RESPONSE_FOOTER ) );


    return result;
}

It seems to me that copying buffer to string to append it then to vector is not good idea. How can I do it more effectively?

7
  • 2
    I would suggest following optimizations. 1. Reserve the space in result beforehand for all the contents. 2. Get the streambuf instance from the buffer and then use its sgetn function to directly populate the result vector Commented Oct 23, 2018 at 11:59
  • Are you intending to have results with two deep <tr>? Commented Oct 23, 2018 at 12:01
  • @NishantSingh, seems like you missed couple of lines Commented Oct 23, 2018 at 12:02
  • @Caleth, thanks. Sure, no. But the main question is about effective copying stringstream to vector Commented Oct 23, 2018 at 12:03
  • @Yura srry I have updated the comment Commented Oct 23, 2018 at 12:06

3 Answers 3

3

You can use std::istreambuf_iterator to copy characters directly from a stringstream to a vector, like this:

std::vector<char> sstreamToVector(std::stringstream& src)
{
    // make sure that "get" position is at the beginning and "put" is at the end of the stream
    src.seekg(0);
    src.seekp(0, std::ios::end);

    std::vector<char> dst;
    dst.reserve(src.tellp());
    std::copy(std::istreambuf_iterator<char>(src),
              std::istreambuf_iterator<char>(),
              std::back_inserter(dst));
    return dst;
}

str.tellp() returns number of characters written to the stream (p stands for "put area"), so it can be used to allocate enough space in the buffer.

Sign up to request clarification or add additional context in comments.

3 Comments

I would probably seek put to end and get to start, first, just to be safe, but otherwise 👌
@LightnessRacesinOrbit good point, that (+maybe restoring them at the end) should be done when putting this in a separate function
Ooh restoring is a good idea. Some people would say that the cursor's state from outside the function should be abided by, and should be at the end after all the bytes are "consumed". but since we never really consume anything from a stringstream (at least not destructively), it doesn't matter too much as long as you document your choice.
0

The thing that I understand you to be trying to avoid is the movement of chars between containers. It's not too hard to dump everything in result directly in this simple case:

auto result = accumulate(cbegin(request->paramsMap), cend(request->paramsMap), vector<char>{ '<', 't', 'r', '>', '<', '/', 't', 'r', '>' }, [](auto& init, const auto& i) {
    const char start[] = { '<', 't', 'r', '>', '<', 't', 'd', '>' };
    const char middle[] = { '<', '/', 't', 'd', '>', '<', 't', 'd', '>' };
    const char finish[] = { '<', '/', 't', 'd', '>', '<', '/', 't', 'r', '>' };

    init.insert(prev(cend(init), 5U), cbegin(start), cend(start));
    init.insert(prev(cend(init), 5U), i.first, next(i.first, strlen(i.first)));
    init.insert(prev(cend(init), 5U), cbegin(middle), cend(middle));
    init.insert(prev(cend(init), 5U), i.second, next(i.second, strlen(i.second)));
    init.insert(prev(cend(init), 5U), cbegin(finish), cend(finsh));
    return init;
} );

copy(cbegin(result), cend(result), ostream_iterator<char>{ DEBUG_LOG_F });
result.insert(cbegin(result), RESPONSE_HEADER, next(RESPONSE_HEADER, strlen(RESPONSE_HEADER)));
result.insert(cend(result), RESPONSE_FOOTER, next(RESPONSE_FOOTER, strlen(RESPONSE_FOOTER)));
return result;

5 Comments

How would the compiler prevent that here? (Hint: it won't!)
Your XML slashes are backwards and you didn't escape the erroneous resulting backslashes.
@LightnessRacesinOrbit I had hoped that the compiler would recognize the construction of result here and move the contents of the OP's buffer, RESPONSE_HEADER, and RESPONSE_FOOTER... but even typing that out I'm way too optimistic... I'll edit.
Well I meant your claim about optimisation of the original code, but that too!
@LightnessRacesinOrbit Yeah really, accumulate is really doing a copy internally anyway right now. P0616R0 should fix that in C++20 though...
-1

Just use the stringstream for everything. joe_chip demonstrates how to copy directly from the stringstream.

std::stringstream buffer;

buffer << RESPONSE_HEADER;

for( auto param : request->paramsMap )
{
    buffer << "<tr><td>" << param.first << "</td><td>" << param.second << "</td></tr>\n";
    DEBUG_LOG_F << "<tr><td>" << param.first << "</td><td>" << param.second << "</td></tr>\n";
}

buffer << RESPONSE_FOOTER;

return sstreamToVector(buffer);

10 Comments

@NishantSingh. Don't you think that std::string str = buffer.str(); will slow down the program? If I do so, I instantiate memory once again only to get data from stringstream. It seems for me unnecessary
@Yura yes. but this is easier to understand
just don't do the string conversion, after populating the stringstream, get the streambuf instance and then use its sgetn function to populate the result vector. Anyways solution provided by joe_chip seems to doing the same thing, so it should be equally efficient
@Caleth, agree. But I think writing in C++ we should be focused on effectiveness. At least I try to learn approaches. And that was the question. Anyway thanks for ideas
@Yura: That's because you made a new buffer. The trick is to just copy straight out of the original one. Joe showed you how.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.