The following code is a minimal example from a project that I'm working on. The main question is that I want to cut down the number of calls to the copy constructor, but it is not clear to me the right way to do this.
#include<iostream>
class MyClass
{
public:
MyClass() {std::cout << "Default Constructor\n";}
MyClass(const MyClass &input) {std::cout << "Copy Constructor\n";}
MyClass & operator=(const MyClass &input)
{std::cout << "Assignment\n"; return *this;}
MyClass & operator+=(const MyClass &input) {return *this;}
friend MyClass operator+(MyClass lhs,const MyClass &);
};
MyClass operator+(MyClass lhs,const MyClass &rhs)
{lhs+=rhs;return lhs;}
int main()
{
MyClass a,b,c;
c=a+b;
return 0;
}
When I run the code, the output is:
Default Constructor
Default Constructor
Default Constructor
Copy Constructor
Copy Constructor
Assignment
The three default constructors are called in the construction of a, b, and c.
The two copy constructors are called for the first argument in operator+ and the return for operator+.
The assignment assigns the result from assigning a+b to c.
Main Question: In my application, the copy constructor is expensive (it involves memory allocation). Assignment, on the other hand, is relatively cheap. What is the proper way to have fewer calls to the copy constructor?
I've considered a few solutions, but none make me happy:
As I understand, from reading, the operator+ should not have a reference for the first argument since this helps with chaining temporaries. Therefore, this copy constructor seems unavoidable.
The following code is significantly faster (due to no copy constructor calls): c = a; c += b;
I could write my code using this format, but this requires more a more delicate approach. I would prefer the compiler to be smarter than for me to make these tweaks myself.
I could implement a function add(MyClass &,const MyClass &,const MyClass &);
but this loses the ease of using the addition operator (and requires a lot of (mindless) coding due to the number of different data types that I'm using).
I've looked at the questions, but I don't see any suggestions that might improve performance in this case:
Copy constructor called twice, Copy constructor called twice, and Conditions for copy elision
Responses to comments:
The private data includes MPFR's and MPFI's and the constructor includes initialization of this data. Perhaps a different implementation of the constructors would be appropriate, but I'm not sure.
I considered a move constructor, but there are times when I want a copy copy assignment as well. From cppreference it appears that these cannot coexist (or at least there was an error when I tried it at first). It appears that this should be the best option.
In other to minimize copy constructor calls, I recommend you define the move constructor and that you perfect-forward your operator arguments. Move constructor:
MyClass(MyClass &&input) {std::cout << "Move Constructor\n";}
Perfect-forwarding operator:
template<typename T>
friend MyClass operator+(T &&lhs,T &&rhs) {return std::forward<T>(lhs);}
With the right calls, your operator will involve a move constructor instead of a copy constructor. For instance, if you add objects that come out of a function and store the result immediately (e.g. MyClass c=a+b;
instead of MyClass c;c=a+b;
), thanks to RVO you can save the copy constructor.
Let's say you have a function that returns a MyClass
instance:
MyClass something() {return MyClass();}
If you add the function return values and store them immediately, e.g.:
MyClass c=something()+something();
Then no copy constructor will be ever involved.
I have put a series of examples here where I used the const MyClass&
parameter with operator+
and perfect-forwarding parameters with operator-
. You can see that it makes a difference in the last example but not in all the other ones. That's why I said "with the right calls". If you have to manipulate objects that can be forwarded like that, it might be worth a shot.