1

Hi i am trying to understand how copy constructor works and looking at an example. The example is as follows:

{//new scope
Sales_data *p = new Sales_data;
auto p2 = make_shared<Saled_data>();
Sales_data item(*p); // copy constructor copies *p into item
vector<Sales_data> vec;
vec.push_back(*p2);// copies the object to which p2 points
delete p;
}

My question is :

  1. Why it is written that "copy constructor copies *p into item"? I mean, item is direct initialized. If we would have written Sales_data item = *p; then it will be called copy initialized, so why they have written copy constructor copies *p into item in the comment.

Now, to verify this for myself, i tried creating a simple example myself, but there also i am unable to understand the concept properly. My custom example is as follows:

#include<iostream>
#include<string>

class MAINCLASS{
  private:
    std::string name;
    int age =0;
  public:
    MAINCLASS(){
        std::cout<<"This is default initialization"<<std::endl;
    }
    MAINCLASS(MAINCLASS &obj){
        std::cout<<"This is direct initialization"<<std::endl;
    }
    MAINCLASS(const MAINCLASS &obj):name(obj.name),age(obj.age){
        std::cout<<"This is copy initialization"<<std::endl;
    }
};

int main(){
    MAINCLASS objectone;
    MAINCLASS objecttwo =objectone;
    MAINCLASS objectthree(objectone);
    return 0;
}

Now when i run this program, i get the following output:

This is defalut initialization

This is direct initialization

This is direct initialization

My question from this program is as follws:

  1. Why are we not getting the output "this is copy initialization" in the second case when i write MAINCLASS objecttwo =objectone;? I have read that in direct initialization function matching is used and in copy constructor , we copy the right hand operand members into left hand operand members. So when i write MAINCLASS objecttwo =objectone; it should call the copy constructor and print "this is copy initialization" on the screen. But instead it is direct initializing the object. What is happening here?
Jason
  • 36,170
  • 5
  • 26
  • 60
  • Please show `Sales_data` definition. Does it have `Sales_data(Sales_data&)` constructor? – 273K Mar 11 '21 at 05:57
  • @S.M. `Sales_data` has the following constructors: `Sales_data(const Sales_data&); `,`Sales_data() = default;`,`Sales_data(const std::string &s, unsigned n, double p);`,`explicit Sales_data(const std::string &s);`,`explicit Sales_data(std::istream&); `. – Jason Mar 11 '21 at 06:15
  • Please add all relevant details (such as the definition of `Sales_data`) to the question, not as a comment. – cigien Mar 11 '21 at 13:33

3 Answers3

2

Don't confuse copy construction and copy initialisation. You can copy-construct using direct or copy initialisation.

Copy initialisation refers to a set of initialisation syntax and semantics. This includes the T a = b syntax.

The copy constructor is a special class method that takes an argument of said class. This method should only take one parameter (both T& or const T& will do). Copy construction occurs when that function is called.

With this in mind, we can go on to answer your questions.

  1. Why it is written that "copy constructor copies *p into item"? I mean, item is direct initialized. If we would have written Sales_data item = *p; then it will be called copy initialized...

Both Sales_data item = *p and Sales_data item(*p) call the copy constructor. But, the former uses copy initialisation (T a = b), whereas the latter uses direct initialisation (T a(b)).

  1. Why are we not getting the output "this is copy initialization" in the second case when i write MAINCLASS objecttwo =objectone;?

Actually, the issue here isn't whether it's copy/direct initialised. This is an issue of lvalue/rvalue overload resolution.

Consider the following program:

#include <iostream>

void f(int& i) { std::cout << "int&\n"; }
void f(const int& i) { std::cout << "const int&\n"; }

int main() {
    f(1); // f(const int&)
    
    int i = 2;
    f(i); // f(int&)
}

f is chosen based on whether the value passed is lvalue or rvalue. In the first case, 1 is an rvalue, so f(const int&) is called (see this). In the second case, i is an lvalue, and f(int&) is chosen since it's more general.

So in your case, both MAINCLASS objecttwo =objectone; and MAINCLASS objectthree(objectone); call the copy constructor. And again, the former uses copy initialisation, whereas the latter uses direct initialisation. It's just that both of these calls choose the non-const ref overload instead: MAINCLASS(MAINCLASS&).

TrebledJ
  • 8,713
  • 7
  • 26
  • 48
  • Okay so i have read the official documentation and your explanation(which is nice and essentially the same) and i am summarising what i understood. Can you tell if that is right? So, first both `T obj2 = obj1;` and `T obj3(obj1);` use the copy constructor. But the `T obj2=obj1` used copy initialization and `T obj3(obj2);` uses direct initialization. Now the next step is the compiler will try to find the best possible match for each of those case.If it finds a match then it will use that constructor which is what is happening in our case. Is that right? – Jason Mar 11 '21 at 06:44
  • Yep, pretty much. Again, overload resolution (i.e. choosing which function to call) is the main concern of your second question, and it's a bit more intricate. But the book you're reading would eventually address it, I think. – TrebledJ Mar 11 '21 at 06:47
  • Okay, so the Sales_data class has the following constructors: `Sales_data(const Sales_data&);` ,`Sales_data() = default;`,`Sales_data(const std::string &s, unsigned n, double p);`,`explicit Sales_data(const std::string &s);`,`explicit Sales_data(std::istream&);` As you can see there is a copy constructor there which will be called when we write `Sales_data item=*p`. Now if i add one more constructor say `Sales_data(Sales_data&);` then we could no longer write in the comment that "copy constructor is used" since this time it will not be used? Is that right? – Jason Mar 11 '21 at 07:13
  • 1
    `Sales_data(Sales_data&);` is still a copy constructor (albeit one with a different signature). `Sales_data item = *p` would call this. So the comment still applies. – TrebledJ Mar 11 '21 at 07:30
  • What about the copy assignment operator=. Similar to copy constructor, does `Sales_data operator=(Sales_data&);` and `Sales_data operator=(const Sales_data&);` are both copy assignment operator? – Jason Mar 11 '21 at 07:51
  • [Yes](https://en.cppreference.com/w/cpp/language/copy_assignment). – TrebledJ Mar 11 '21 at 07:53
  • When i write `Sales_data(const Sales_data&)=delete;` then does this delete only this version of copy constructor or does it also delete the `Sales_data(Sales_data&)` version? – Jason Mar 11 '21 at 09:36
  • Just that version. – TrebledJ Mar 11 '21 at 09:52
2

Despite the poor choice of name, copy initialization is orthogonal to copy constructors.

A copy constructor is any constructor whose first parameter is a lvalue reference to its class type, and can be called with just one argument. It's just a constructor that can initialize new objects from existing objects. That's pretty much all there is to it. Both the constructors you declared are in fact copy constructors. This one would be too

MAINCLASS(MAINCLASS volatile &obj, void *cookie = nullptr) {
  // .. Do something
  // This is a copy c'tor since this is valid:
  // MAINCLASS volatile vo;
  // MAINCLASS copy1_vo(vo);
}

And as the other answers noted copy initialization is simply the name for a family of initialization contexts. It includes initialization involving =, passing arguments to functions, return statements and throw expressions (and I'm probably forgetting something). Direct initialization involves other contexts.

A copy constructor can be used in any of the above. Be it copy initialization or direct initialization. The difference between the two - as appertains to constructors - is how an overload set of constructors is built. Copy initialization doesn't make use of constructors declared explicit. For instance, in this example

struct Example {
  Example() = default;
  explicit Example(Example const&) {}
};

int main() {
  Example e;
  Example e1(e); // Okay, direct initialization 
  Example e2 = e1; // Error! Copy initialization doesn't make use of explicit constructor
}

Even though we have a copy constructor, it can't be called in a copy-initialization context!


As far as the unexpected print out of your program, it's simply a matter of overload resolution choosing a more matching function. Your origin object is not declared const. So binding it to a non-const lvalue reference is simply the preferred choice in overload resolution.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Okay this makes more sense. Thanks. – Jason Mar 11 '21 at 07:42
  • When we write `Example obj;` then does the default constructor is called with a `*const this` as parameter? I mean we only have a single default constructor for both const and nonconst objects? For example i can write `const Example a;` and `Example b;`. What is happening in these cases? Since we only have one default constructor, how is this case handled? Is the address of object(&a) is implicitly passed as argument ? just like for ordinary member functions? – Jason Mar 11 '21 at 09:24
  • @JasonLiam - That's veering very far away from the subject of your post. Suffice it to say, inside a constructor, `this` never points at a const object. Constructors always consider an object modifiable. The constness only comes into effect once an object is fully initialized. – StoryTeller - Unslander Monica Mar 11 '21 at 09:33
  • Yeah that is what i was thinking that constness only comes into play when we already have an object. But where can i read more about my last question? – Jason Mar 11 '21 at 09:35
  • @JasonLiam - I'm not sure I understand your last question entirely. The address of the current object is always snuck into a member function via `this`. The compiler handles that. – StoryTeller - Unslander Monica Mar 11 '21 at 09:38
  • Is it correct to say that when i define an object either of const or nonconst of a class type for example `Example e;` or `const Example e;` then it doesn't matter whether the object is const. Because constness is "added" after the constructor body has been executed. Is my above statement correct? If yes then how is constness added? If not then when is constness added? Thanks – Jason Mar 11 '21 at 09:41
1

Copy initialization and direct initialization is based on the syntax used to construct.
See Confusion in copy initialization and direct initialization.

Which constructor gets invoked is based on overload resolution (and not the syntax to construct) The compiler invokes the function which best matches the passed arguments to the defined parameters.

In your example since objectone is non-const, the best match is the copy constructor with a non-const parameter. Since the other copy constructor has a const& parameter, it will get invoked for a const object.

Rewriting your example:

#include<iostream>
#include<string>

class MAINCLASS {
private:
    std::string name;
    int age = 0;
public:
    MAINCLASS() {
        std::cout << "This is default initialization" << std::endl;
    }
    MAINCLASS(MAINCLASS& obj) {
        std::cout << "This is copy constructor with non-const reference parameter" << std::endl;
    }
    MAINCLASS(const MAINCLASS& obj) :name(obj.name), age(obj.age) {
        std::cout << "This is copy constructor with const reference parameter" << std::endl;
    }
};

int main() {
    MAINCLASS objectone;
    const MAINCLASS const_objectone;

    MAINCLASS objecttwo = objectone;  // copy initialization of non-const object
    MAINCLASS objectthree(objectone); // direct initialization of non-const object

    MAINCLASS objectfour = const_objectone; // copy initialization of const object
    MAINCLASS objectfive(const_objectone);  // direct initialization of const object
    return 0;
}

The output would be:

This is default initialization
This is default initialization
This is copy constructor with non-const reference parameter
This is copy constructor with non-const reference parameter
This is copy constructor with const reference parameter
This is copy constructor with const reference parameter
ap-osd
  • 2,624
  • 16
  • 16
  • But i have read that "copy initialization happens when we define variables(objects) using an =". So why in your example, even though you are direct initializing it will call the copy constructor. I think the compiler is free to bypass this copy constructor initialization. And that is why in my custom example it is doing so. According to your example, since their is no constructor that takes a const MAINCLASS& as argument except the copy constructor , that is why when the compiler doesn't find any matching constructor , it will try the copy constructor instead. Is this right? – Jason Mar 11 '21 at 06:11
  • See https://stackoverflow.com/questions/1051379/is-there-a-difference-between-copy-initialization-and-direct-initialization for an explanation of copy-initialization and direct-initialization. – ap-osd Mar 11 '21 at 07:25
  • Are there two version of the default constructor? One `MAINCLASS();` and the other `MAINCLASS() const;`. First for nonconst object and other for const objects? – Jason Mar 11 '21 at 09:14
  • There is no special constructor for a `const` object. `const` is an object qualifier to indicate that the object (after creation) cannot be modified. – ap-osd Mar 11 '21 at 13:15