2

The following quote from my C++ book:

When we use direct initialization, we are asking the compiler to use ordinary function matching to select the constructor that best matches the arguments we provide. When we use copy initialization, we are asking the compiler to copy the right-hand operand into the object being created, converting that operand if necessary.

For me this bolded bit produces a bit of ambiguity. It makes it sound like the right-hand operand is converted to the class-type and then the copy-constructor is used, for instance;

string s = "hello";

would become...

string s = string("hello"); 

which uses the copy constructor. If this is true then my test program;

#include <iostream>
using namespace std;

class A{
public:
    A(const A& b): data(b.data) { cout << "The first way" << endl;}
    A(const char* c): data(c) { cout << "The second way" << endl;}
    string data;

};
int main(){
    A first("hello");
    A second = "sup";
}

Should be producing "The second way, The second way, The first way". However it instead prints "The second way, The second way". From this I would've concluded it is using the const char* constructor and not the copy-constructor. I would be fine with this except later it says...

During copy initialization, the compiler is permitted (but not obligated) to skip the copy/move constructor and create the object directly. That is, the compiler is permitted to rewrite

string null_book = "9-999-99999-9"; 

into

string null_book("9-999-99999-9");

However, even if the compiler omits the call to the copy/move constructor, the copy/move constructor must exist and must be accessible (e.g., not private) at that point in the program.

I'm not sure why the copy constructor even needs a mention in these examples, doesn't

 string null_book = "9-999-99999-9"

always implicitly mean that the const char* constructor is being used anyway? Indeed it makes less sense to me that the copy-constructor needs to be defined in order for the above to work. But alas, if I put the "const A&" constructor as private (the rest public) then my program will not run. Why must the copy-constructor be defined for implicit conversions that don't even involve it? What constructor does "string null_book = "9-999-99999-9"" use?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Silversonic
  • 1,289
  • 2
  • 11
  • 26
  • 3
    You've already quoted the answer to your question. – Ben Voigt Aug 25 '15 at 23:07
  • 1
    The conversion occurs through the `A(const char*)` constructor followed by the (elided) call to the copy-constructor. – David G Aug 25 '15 at 23:09
  • @Ben Voigt But it said the compiler was not obligated to rewrite it like that. So it seems like I couldn't know whether my program was doing `A b("sup")` or `A b = "sup"` – Silversonic Aug 25 '15 at 23:13
  • 2
    Pretty much. You know that `A(char*)` will be called in both cases, and that `A(const A&)` will never be called in the first case. Note that copy elision applies in a lot more scenarios than copy-initialization, for example there's return value optimization (RVO) and named return value optimization (NRVO), so copy constructors shouldn't have side effects (except allocations that are completely reversed by the destructor). – Ben Voigt Aug 25 '15 at 23:21
  • @Silversonic Why would you need to know whether the copy ctor is going to be called? – curiousguy Aug 25 '15 at 23:48
  • @curiousguy What if my constructor initialiser list varied between the copy-constructor and the const char* so that my final object's data members differed depending on which was called? – Silversonic Aug 25 '15 at 23:50
  • @Silversonic You mean, what if your copy ctor is not making an equal object? Then you have a problem. – curiousguy Aug 26 '15 at 00:24
  • @curiousguy I don't plan on doing it, but I do like asking "what if"? Maybe there could be some situation where someone didn't want to make a completely equal object. Not that I know any. – Silversonic Aug 26 '15 at 00:25
  • 1
    @Silversonic Let's say you make a + operator that actually prints a file on console. Or a comparison operator (== != < ...) that returns a string. You would only confuse the reader. The compiler will call these according to language rules. There is no optimisation allowed, even for reflexive comparisons (`x==x`), the user defined operator must be called, the compiler cannot optimise to true. (Silly fake comparisons operators won't play well with the STL, obviously.) – curiousguy Aug 26 '15 at 00:30
  • 1
    But the copy ctor is special. The copy ctor is called by the compiler, not just by you. The semantics of the copy ctor are part of the semantic of C++. **The standard was written with the idea** that a copy ctor must really make a copy, and **that optimisations that break your program when you have fun with copy ctor semantics are OK.** – curiousguy Aug 26 '15 at 00:31
  • 1
    And that applies to move ctor too. A move ctor semantic is: a copy ctor followed by arbitrary change to the object's value, a move ctor must also make an equal copy (but then it can alter the original). – curiousguy Aug 26 '15 at 00:34
  • @curiousguy I see, thanks. – Silversonic Aug 26 '15 at 00:42
  • Since C++17, what you described in the 2nd part of your question is not a problem. The compiler is obligated to skip the copy constructor and the code above will compile/ run even if you make `A(A const&)` constructor private. More details [here](https://en.cppreference.com/w/cpp/language/copy_elision). – koushik Jun 09 '19 at 06:50

1 Answers1

6

string null_book = "9-999-99999-9"; means string null_book = string("9-999-99999-9");.

This uses the const char * constructor to construct a temporary object, and then null_book is copy/move constructed from the temporary object, and then the temporary object is destroyed.

(Copy/move construction means that a move constructor is used if available; otherwise a copy constructor).

However this scenario is also eligible for copy elision. You actually quoted the copy elision specification in your question so I won't repeat it.

The compiler may choose to use the same memory space for both null_book and the temporary object, and omit the calls to the temporary object destructor and the null_book copy/move constructor.

In your case the compiler did indeed choose to do that, which is why you do not see any copy constructor output.

Some compilers allow copy elision to be disabled by a switch, e.g. gcc/clang -fno-elide-constructors.

More info about copy elision

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Thanks. My main concern was that my program could be different depending on how my compiler ran, as pointed out the info it's better to avoid elision if it affects observable behaviour. Maybe it also adds another reason to sometimes declare `explicit` constructors. – Silversonic Aug 26 '15 at 00:12
  • 1
    It's considered good style to avoid giving copy constructors side-effects other than actually doing the required construction; making this a non-issue – M.M Aug 26 '15 at 00:32
  • Using an implicit constructor call works with a deleted copy constructor and move constructor. Are you sure that copying or copy elision happens here? – Neil Kirk Aug 26 '15 at 01:02