Need4Steed
Need4Steed

Reputation: 2180

string or const char*, which one is more efficient to be used as constructor parameter

I discovered that the sizeof(string) is 28B for VC and 32B for GCC respectively(the data part allocated on the heap is not included). This makes me very skeptic about the efficiency of using string as a parameter of a c++ constructor. So I conducted a little experiment as below:

class Foo
{
    static int inc;
    int val;
    int id;
public:
    Foo(int value) :val(value), id(Foo::inc++) {
        std::cout << to_string() << " being constructed\n";
    }
    ~Foo() {
        std::cout << to_string() << " being destroyed\n";
    };
    Foo(Foo const &other) :Foo(other.val) {
        std::cout << other.to_string() << " being copied\n";
    }
    Foo& operator=(Foo const &other) {
        val = other.val;
        std::cout << other.to_string() << " being copied\n";
    }
    Foo(Foo &&other) :Foo(other.val) {
        std::cout << other.to_string() << " being moved\n";
    }
    Foo& operator=(Foo &&other) {
        val = other.val;
        std::cout << other.to_string() << " being moved\n";
    }
    int value() const noexcept { return val; }
    std::string to_string() const {
        return std::string("Foo_") + std::to_string(id) + "_(" + std::to_string(val) + ")";
    }

};
int Foo::inc = 0;


struct Bar {
    Bar(Foo const &foo) :val(foo) {}
    Bar(Foo &&foo) :val(std::move(foo)) {}
private:
    Foo val;
};

//------- run it ------
Bar bar {42};

This is the result I got:

Foo_0_(42) being constructed
Foo_1_(42) being constructed
Foo_0_(42) being moved
Foo_0_(42) being destroyed
Foo_1_(42) being destroyed

Obviously, a temporary Foo instance was created on the spot of the argument of the ctor. So I suppose when I pass a const char* to a constructor that expects a string, the same procedure would happen, i.d. a temp str >= 28/32 bytes would be created and dropped immediately after being moved/copied. That kind of cost just makes me uncomfortable. Don't get me wrong, I will use a string as a data member of the class, it's only the formal argument type of the ctor that worries me.

Anyhow, if I replace the string parameter with a const char*, given that I can always resort to string::c_str(), I assume I would never need to pay such a cost, would I? I'd like to hear your perspective. If there is anything wrong with my analysis, please just point it out. If there is some mechanism which can eliminate the cost of the temp string, please teach me how would it work. If the temp string is inevitable, do I have to overload the ctor with both const char* and string&&, one to avoid temp string and one for rvalue string to avoid deep-copy? Thanks in advance!

Upvotes: 1

Views: 293

Answers (2)

Steve Lorimer
Steve Lorimer

Reputation: 28659

Note that your question explicitly makes reference to constructor arguments, so this answer refers to those.

Other reasoning applies for assignment operators and setters. (See this question for further details)


If you're going to be storing the string in your object as a data member, then the creation of a string will inevitably have to occur.

As such, the easiest option is to perform the creation of the string as a side-effect of your constructor arguments, and then move it into your data member.

In the following examples, no temporary strings are created and then destroyed. All allocated resources are stored in data_ (by using move)

class Foo
{
public:    
    Foo(std::string data)
        : data_(std::move(data))
    {}
private:
    std::string data_;
};

int main()
{
    const char* foo = "...";
    Foo a(foo);

    std::string bar = "...";
    Foo b(std::move(bar));

    Foo c("...");
}

In terms of your sizeof analysis, note that this size has nothing to do with the dynamic (heap) storage std::string will sometimes allocate to store the character data.

This is just the size of the internal data members, and these are allocated on the stack (automatic storage duration), which is extremely fast.

Stack allocation typically shouldn't be of any concern to you.

Upvotes: 2

JVApen
JVApen

Reputation: 11317

std::string ain't the cheapest class for memory because of small object optimization. This makes it indeed questionable to use it, though it also has nice API functions and a lot of safety/usability.

Should you worry?

If the code ain't performance critical, don't worry! Gaining a couple of microseconds at the lifetime of your program ain't worth the effort.

Instead, run a profiler over you code and fix the bottlenecks you can find.

Passing std::string by value/construction ref

Assuming you are using an optimizing compiler and pass the flag O2, O3 ... the overhead can be removed by the compiler in a lot of cases. If needed, implement the For in the header. If you pass by value, don't forget the std::move.

Passing std::string_view

In newer standards of the standard library, string_view is available, if not, you can easily copy it from GSL ... This class is similar to a raw char pointer (+ size) and can be used for places where you like to pass strings with needing the std::string

In the end, if you like to store the string, you will still have to convert to std::.string

Upvotes: 3

Related Questions