3

I am aware about the + operator to join 2 std::string objects. And this method to join multiple strings. But I am wondering about the performance. This answer tells why + operator is inefficient as compared to a method (in Python). Is there a similar function to join multiple strings in C++ standard library?

8
  • 1
    There is no performance difference, it is quite literally syntactic sugar. Commented Dec 14, 2017 at 2:03
  • 2
    Possible duplicate of Efficient string concatenation in C++ Commented Dec 14, 2017 at 2:06
  • You could use a stream but that's at best slightly faster Commented Dec 14, 2017 at 2:07
  • 1
    @AustinBrunkhorst that isn't true, is it - doing string a = b + c + d + e; will invoke operator+ multiple times, returning a temporary each time. A variadic function that did concat(a, b, c, d) could compute the length needed for the result, allocate one array, and perform the concatenation. stringstream is, of course, much more general. Commented Dec 14, 2017 at 2:08
  • @sfjac of course, but I don't think that's what this question is asking. If it is, then it should be clarified. Commented Dec 14, 2017 at 2:11

3 Answers 3

3

There is no performance difference between an operator overload and method call -- depending on the calling context. At the point you should be concerned about this, you're micro optimizing.

Here's an abstract example demonstrating the concept.

class MyString 
{
public:
    // assume this class is implemented

    std::string operator+(const std::string &rhs) const
    {
        return concatenate(rhs);
    }

    std::string concatenate(const std::string &rhs) const
    {
        // some implementation
    }
};

MyString str1, str2;

// we can use the operator overload
std::string option1 = str1 + str2;

// or we can call a method
std::string option2 = str1.concatenate(str2);

Operator overloads exist (for the most part) to avoid typing out lengthy method calls like the latter example. It makes code more clean and concise.

If you were specifically talking about the performance of more than 2 strings, this is a different scenario. It's more performant to batch objects into one method call, as it avoids constructing more temporary objects than necessary. You won't be able to do this without a new data structure to do the heavy lifting for you.

Using the class above, we'll look at concatenating a bunch of objects together using the + operator in one expression.

std::string bigConcatenation = str1 + str2 + str1 + str2 + str1;

Firstly, you probably wouldn't be doing this if you were concerned about performance in the first place. That said, here's a pretty decent approximation of what would be happening (assuming no optimizations done by the compiler).

std::string bigConcatenation = str1;

bigConcatenation = bigConcatenation + str2; 
bigConcatenation = bigConcatenation + str1;
bigConcatenation = bigConcatenation + str2;
bigConcatenation = bigConcatenation + str1;

The reason why this is not ideal, is each assignment creates a temporary object, adds them together, then assigns the result back to bigConcatenation.

Without using any extra containers, according to this answer, the most performant way of doing this would be something like this (hint: no temporaries are created in the process).

std::string bigConcatenation = str1;

bigConcatenation += str2;
bigConcatenation += str1;
bigConcatenation += str2;
bigConcatenation += str1;
Sign up to request clarification or add additional context in comments.

2 Comments

The last example is certainly more efficient, but to have a good claim on most efficient bigConcatenation should probably be default-initialized, then bigConcatenation.reserve(str1.size() * 3 + str2.size() * 2), then a series of operator+= calls.
I mean, everything should be taken with a grain of salt :)
1

Concatenating strings is rarely enough a bottleneck in C++ that almost nobody ever attempts to optimize it much.

If you get into a situation where it's honestly important to do so, it's fairly easy to avoid the problem though, with fairly minimal change, so a = b + c + d + e; stays a = b + c + d + e; instead of something like a.concat(b, c, d, e); (which, I'd add, is actually quite non-trivial to make work well). Depending on the types involved, you may need to add code to convert the first item in the list to the right type. For the obvious example, we can't overload operator+ to work on string literals, so if you wanted to concatenate string literals together, you'd have to explicitly convert the first to some other type.

The "trick" is in something called expression templates (except that in this case it doesn't really even need to be a template). What you do is create a string builder object that overloads its operator+ to just store pointers/references to the source strings. That, in turn, overloads a conversion to std::string that adds up the lengths of all the source strings, concatenates them together into an std::string, and finally returns that entire string. For example, code can look like this:

#include <string>
#include <iostream>
#include <vector>
#include <numeric>
#include <cstring>

namespace string {

    class ref;

    class builder {
        std::vector<ref const *> strings;
    public:
        builder(ref const &s) { strings.push_back(&s); }

        builder & operator+(ref const &s) {
            strings.push_back(&s);
            return *this;
        }

        operator std::string() const;
    };

    class ref {
        char const *data;
        size_t N;
    public:
        ref(char const *s) : data(s), N(std::strlen(s)) {}
        ref(char const *s, size_t N) : data(s), N(N-1) {}

        size_t length() const { return N; }

        operator char const *() const { return data; }

        builder operator+(ref const &other) {
            return builder(*this) + other;
        }
    };

    builder::operator std::string() const {
        size_t length = std::accumulate(strings.begin(), strings.end(),
            size_t(),
            [](size_t a, auto s) { return a + s->length(); });
        std::string ret;
        ret.reserve(length);
        for (auto s : strings)
            ret.append(*s);
        return ret;
    }
} 

string::ref operator "" _s(char const *s, unsigned long N) { return string::ref(s, N); }

int main() {
    std::string a = "b"_s + "c" + "d" + "e";
    std::cout << a << "\n";
}

Note that as the code is written above, you (at least theoretically) gain some run-time efficiency if you converted all the literals to string_refs up-front:

    std::string a = "b"_s + "c"_s + "d"_s + "e"_s;

This way, the compiler measures the length of each literal at compile time, and passes the length directly to the user-defined literal operator. If you just pass string literals directly, the code uses std::strlen to measure the length at run time instead.

Comments

0

you can use a stringstream remember the to add the directive #include
into the header file or source file of your class. Whichever one has your method definitions.

Comments

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.