2

The title says it all. However, please take string as a placeholder for any class.

std::string s1("hello");  // construct from arguments
std::string s2 = "hello"; // ???
std::string s3;           // construct with default values
s3 = "hello";             // assign

I wonder if the statement for s2 does the same as for s1 or for s3.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Fabian
  • 4,001
  • 4
  • 28
  • 59
  • 1
    Possible duplicate of [Is there a difference in C++ between copy initialization and direct initialization?](http://stackoverflow.com/questions/1051379/is-there-a-difference-in-c-between-copy-initialization-and-direct-initializati) –  Jan 25 '17 at 08:19
  • Also: [Initialisation and assignment](http://stackoverflow.com/questions/7350155/initialisation-and-assignment) –  Jan 25 '17 at 08:23

4 Answers4

14

The case of s2 is copy initialization. It's initialization, not assignment as case of s3.

Note that for std::string, the effect is the same for s1 and s2, the apporiate constructor (i.e. std::string::string(const char*)) will be invoked to construct the object. But there's a different between copy intialization and direct initialization (the case of s1); for copy intialization, explicit constructor won't be considered. Assume that std::string::string(const char*) is declared explicit, that means the implicit conversion from const char* to std::string is not allowed; then the 2nd case won't compile again.

Copy-initialization is less permissive than direct-initialization: explicit constructors are not converting constructors and are not considered for copy-initialization.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
2

In this case, s1 and s2 do exactly the same thing: they both call the constructor to const char*. (Some folk prefer to use = for clarity).

For s3, the default constructor is called, followed by operator= to const char*.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
2

While the end result of all 3 methods is the same (the string will be assigned to the variable), there are certain fundemental differences that go deeper than the syntax. I'll go over all 3 scenarios covered by your 3 strings:

The first case: s1 is an example of direct initialization. Direct initialization covers a number of difference scenarios, yours being defined as follows:

initialization with a nonempty parenthesized list of expressions.

Here, s1 does not have a class data type but a std::string data type, so a standard conversion will take place to convert the data type in the parenthesis to the cv-unqualified version of s1, which is const *char. Cv-unqualified means that there is no qualifier such as (const) or (volatile) attached to the variable. Note that in the case of a direct initialization, it is a lot more permissive than copy-initialization, which is the subject of s2. This is because copy-initialization will only refer to constructors and conversion functions defined by the user that are non_explicit (i.e implicit). On the other hand, direct-initialization considers implicit and explicit constructors and user-defined conversion functions.

Moving on, the second string, s2, is an example of copy initialization. Simply put, it copies the value from the left side to the right side. It is an example of:

when a named variable (automatic, static, or thread-local) of a non-reference type T is declared with the initializer consisting of an equals sign followed by an expression.

The process covered by this method is the same. Since s2 does not have a class data type but a std::string data type, it will use the standard conversion to convert the value of the string on the right side to a value of type const *char on the left. However, if the function is declared explicitly, the standard conversion cannot be made unlike the copy initializer, and the compilation of the code will fail.

See some examples of code comparing to 2 types of initializations. This should clear any confusions from above:

    struct Exp { explicit Exp(const char*) {} }; // This function has an explicit constructor; therefore, we cannot use a copy initialization here
    Exp e1("abc");  // Direct initialization is valid here
    Exp e2 = "abc"; // Error, copy-initialization does not consider explicit constructor
     
    struct Imp { Imp(const char*) {} }; // Here we have an implicit constructor; therefore, a copy initializer can be used
    Imp i1("abc");  // Direct initialization always works
    Imp i2 = "abc"; // Copy initialization works here due to implicit copy constructor 

Moving on to the third case, it is not even a case of initialization, it is a case of assignment. Like you said in your comments, the variable s3 is initialized with a default string. That string is replaced by "Hello" when you use the equal sign to do so. What happens here is that when s3 is declared in string s3;, the default constructor for std::string is called, and a default string value is set. That default string is replaced by hello in the next line when you use the = sign.

If we are looking at which is more efficient in terms of speed when running, the difference is marginal. However, s1 takes the fastest time to run if we solely do this:

    int main(void)
    {
        string a("Hello");
    }

This took the following time and memory to compile and run:

Compilation time: 0.32 sec, absolute running time: 0.14 sec, cpu time: 0 sec, memory peak: 3 Mb, absolute service time: 0,46 sec

If we look at string s2 coded in the following way:

    int main(void)
    {
        string a = "Hello";
    }

Then the total time taken for the program to run is:

Compilation time: 0.32 sec, absolute running time: 0.14 sec, cpu time: 0 sec, memory peak: 3 Mb, absolute service time: 0,47 sec

The runtime using the copy initializer takes 0.01 seconds more to run than the direct initializer. Difference exists, but a marginal one.

The 3rd case with s3, if coded the following way:

    int main(void)
    {
        string a;
        a = "Hello";
    }

Has a total running, compiling time and space taken of:

Compilation time: 0.32 sec, absolute running time: 0.14 sec, cpu time: 0 sec, memory peak: 3 Mb, absolute service time: 0,47 sec

I'd like to point something out here: the runtime difference between the second and third methods is most probably NOT zero; rather it is a time difference less than 0.01 seconds, with the 3rd method taking longer (s3). That is because it has 2 lines of code to operate on; one is the declaration of the variable and the other the assignment of the string to the variable.

Hope this answers your question.

BusyProgrammer
  • 2,783
  • 5
  • 18
  • 31
  • Thank you very much for your detailed answer. For your performance tests, you should have repeated the same step in a loop many times and compute the average time. I believe that this make the difference in performance much clearer. Also, you have to make sure that the compiler does not simply optimize the unused string variable away! – Fabian Feb 01 '17 at 06:03
  • Actually @Fabian, I did run them multiple times, but since the time is precise to only 2 decimal places, from the site I used, the time was __exactly__ the same! What I needed was a more precise time to be able to find the average. Thanks for your feedback nevertheless :-) – BusyProgrammer Feb 01 '17 at 12:22
1

If a class does not have an accessible copy constructor, the second form of initialization is invalid:

[temp]$ cat test.cpp
struct S {
    S(int);
private:
    S(const S&);
};

S s1(3);  // okay
S s2 = 3; // invalid
[temp]$ clang++ -std=gnu++1z -c test.cpp
test.cpp:8:3: error: calling a private constructor of class 'S'
S s2 = 3; // invalid
  ^
test.cpp:4:5: note: declared private here
    S(const S&);
    ^
1 error generated.
[temp]$ 

The difference is that the second one, formally, creates a temporary object of type S, initialized with the value 3, and then copies that temporary into s2. The compiler is allowed to skip the copy and construct s2 directly, but only if the copy would have been valid.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • I think copy ctor is irrelevant here. http://melpon.org/wandbox/permlink/2VI5MmtM8qG73qTs – songyuanyao Jan 25 '17 at 15:01
  • @songyuanyao - I've edited my answer to show the compiler output and switches (which match yours). It's still an error. – Pete Becker Jan 25 '17 at 15:18
  • It might be a C++17 issue. From [here](http://en.cppreference.com/w/cpp/language/copy_initialization), "The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)". [C++14](http://melpon.org/wandbox/permlink/9YhwdZPPV8vIYHN0) vs [C++17](http://melpon.org/wandbox/permlink/VbNcgcySstUZY3RZ) – songyuanyao Jan 25 '17 at 15:27
  • @songyuanyao - C++17 is not (yet) the language definition. Even when it becomes the standard, having an accessible copy constructor will not be "irrelevant" to people using today's compilers. – Pete Becker Jan 25 '17 at 15:38
  • Fair enough. It might be better to mention the potential change in the answer. – songyuanyao Jan 25 '17 at 15:46