Constructor parameter style

你说的曾经没有我的故事 提交于 2019-12-11 09:09:30

问题


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


回答1:


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



回答2:


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;
}



回答3:


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) {};



回答4:


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).



来源:https://stackoverflow.com/questions/16944715/constructor-parameter-style

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!