1

Lets say my file looks like this:

#include <iostream>
#include <string>

using namespace std;

class testclass{
public: string name;
        //testclass(const string& sref){
          //  name = sref;
        //}
        testclass(string str){
            name = str;
        }
        ~testclass(){}
};

int main(){
    testclass t1("constStringRef");
    cout << t1.name << '\n';
}

What are the differences between constructor 1 and 2 given the following constructor-call:

testclass tobj("tmemberstring");

Here is what i thought of:

I know that passing by reference means that you don't pass a copy but due to the string-argument there is a string-initialization at first (in both cases treated like a local variable, i assume), which is then followed by a the initialization of a reference to it in case 1 or a copy to a new string str in case 2. In the end both of the constructors copy the values to the member string name. If my thoughts are correct i would skip one step (copying into string str) if would use the first constructor.

Sidequestions: Are arguments stored in the stack area? And if so how much space would this particular string reference or a reference to any basic data types use?

Hoping for your advice, thanks in advance

5
  • 1
    Everything in your code makes no sense. what are n and s?! Commented Jun 5, 2013 at 16:06
  • You should try to compile you code before bloating it onto StackOverflow! Commented Jun 5, 2013 at 16:10
  • Sorry renamed it to make it clearer and forgot to rename the assignments. Commented Jun 5, 2013 at 16:10
  • 1
    Do neither; if you're going to store a copy of the string in your class, accept the argument by value and then std::move it in the constructor's initializer list. Commented Jun 5, 2013 at 16:14
  • possible duplicate of Preferred parameter passing for constructors Commented Jun 5, 2013 at 16:15

4 Answers 4

2

The easiest way to answer your question is to break down what happens in both cases.

testclass(const string& sref)

  • testclass t1("constStringRef"); first creates a temporary string object from the const char*
  • the constructor is called, the temporary string object is bound to the constructor's const string& argument
  • name is uselessly default-constructed since you didn't use the constructor's initializer list (more on that later)
  • string::operator = is called, making a copy of the const string& argument

Total: 1 copy.

testclass(string str)

  • testclass t1("constStringRef"); first creates a temporary string object from the const char*
  • the constructor is called -- what happens depend on which C++ version you are using:
    • C++03: the temporary string object is copied to the constructor's argument
    • C++11: the temporary is moved into the constructor's argument
  • name is uselessly default-constructed since you didn't use the constructor's initializer list
  • string::operator = is called, making a copy of the string argument

Total: 2 copies in C++03, 1 copy in C++11.


From this, we could believe that a const string& is better. However this is only true in C++03.


C++11 and move semantics

In C++11, it is better (in this very case) to pass the string by value to the constructor, and then move the argument into your class member:

    testclass(string str){
        name = std::move(str);
    }

Let's see what happens now:

  • testclass t1("constStringRef"); first creates a temporary string object from the const char*
  • the constructor is called, the temporary is moved into the constructor's argument
  • name is uselessly default-constructed since you didn't use the constructor's initializer list
  • string::operator = is called, but this time moving the string argument into name

Total: 0 copy!


This is all fine with rvalues, but does this still hold true for lvalues?

string s = "..."; // s has already been constructed some time ago
testclass t1(s);  // what happens during this call?
  • for a constructor that takes const string& (both in C++03 and C++11):

    • s is bound to the const string& argument
    • name is uselessly default-constructed since you didn't use the constructor's initializer list
    • string::operator = is called, making a copy of the const string& argument
    • Total: 1 copy.
  • for a constructor that takes string and then moves it (only in C++11):

    • s is copied to the string argument
    • name is uselessly default-constructed since you didn't use the constructor's initializer list
    • string::operator = is called, but this time moving the string argument into name
    • Total: 1 copy.

Wrapping it up

In C++03, whether you're passing a lvalue or a rvalue doesn't matter, it is always more efficient to use a const string&. As others have mentioned, you may want to overload your constructor to take a const char* argument and thus avoid a useless copy.

In C++11, provided you move the argument into the member variable, a string argument is the same as a const string& argument for lvalues, but it is more efficient for rvalues (no copy needs to be performed at all). So you should use pass-by-value and then move the argument to the member variable.


Last but not least, you noticed that I insisted on uselessly default-constructing name. To avoid it, use the constructor's initializer list rather than an assignment in the constructor's body:

    // C++03
    testclass(const char* str) : name(str) {}       // no copy
    testclass(const string& sref) : name(sref) {}   // 1 copy

    // C++11
    testclass(string str) : name(std::move(str)) {} // 1 copy for lvalues,
                                                    // no copy for rvalues
Sign up to request clarification or add additional context in comments.

Comments

1

In both cases, the constructor accepts a std::string. Since you are calling the constructor with a string literal (a const char*), a temporary std::string is going to be constructed in order to call the constructor. The difference between the two methods is what happens next:

In the case of testclass(const string& sref) a const reference to the temporary string you just created can be taken. In the second case, the string is taken by value, so a second temporary needs+ to be created.

  • note: compilers can sometimes optimize this second temporary away.

As a general rule of thumb, I would suggest using the const& whenever possible.

Note however, that you could avoid constructing the temporary std::string altogether, by simply accepting string literals via a template:

template <size_t N> testclass(const char (&str)[N])
{
  name = str;
}

Note also, that when your constructor is called two things happen. 1) The name member is constructed. 2) The value of the name member is changed. You can initialize and construct the name member in a single step, using an initialization list:

template <size_t N> testclass(const char (&str)[N])
: 
  name (str, N-1)  // minus one to not copy the trailing `\0`.  Optional, depending
{
  name = str;
}

2 Comments

template <size_t N> testclass(const char* (&str)[N]) => This is quite wrong. Here you're taking an array[N] of const char*. Either take just a const char*, or a const char (&str)[N]. Obviously just a const char* is preferable because arrays can decay to pointers, while pointers can't be promoted to fixed-length arrays. Also, why the initialization + assignment?
@syam: Thanks for pointing that out. That's what I get for not compiling.
0

First of alle, better finish your stream lines with "std::endl" not "'\n'". One Constructor expects a reference, the other one a value, but you are passing a C-String which is of value "const char*". Third of all: initialize your member before calling the Constructor-Code. I would recommend the following;

testclass(const char* name) : name(name) {};

3 Comments

cout is typically line buffered, in which case endl vs. \n doesn't make any difference.
@Praetorian: But still, why would forcing a flush be the better advice?
yes i know that i passed a c-string but due to the conversion constructor of string this shouldn't matter. correct me if i'm wrong and thanks for your info on initializing.
0

I know that passing by reference means that you don't pass a copy

That's right, so you should normally prefer testclass::testclass(const std::string&), because it avoids that copy

but due to the string-argument there is a string-initialization at first

yes, there is either a temporary string created and passed as a const ref, or the argument is created directly.

There's another string though: your name member is default initialized only because you didn't use the initializer list. This:

testclass::testclass(const std::string &s) : name(s) {}

initializes name directly, without first default-initializing it and then changing it in the constructor body.


Sidequestions: Are arguments stored in the stack area?

arguments could be held on stack, or in registers, or in any other way a compiler finds that complies with the standard. This is an implementation detail.

... And if so how much space would this particular string reference or a reference to any basic data types use?

In general, a reference is probably at most the size of a pointer (and might be elided entirely).

The thing you haven't considered is that std::string owns a dynamically-allocated copy of your character array, so every std::string you pass by value may perform a dynamic allocation, and then a de-allocation when it goes out of scope. (Some compilers may avoid this with reference-counting, but we're back on implementation details).

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.