Constructors C ++ 11

The new move-constructor / move-operator operator allows us to transfer ownership of objects and thus avoid the use of (expensive) calls to the copy constructor. But is it possible to avoid creating temporary objects (without using return parameters)?

Example. In the code below, the constructor is called 4 times, but ideally I would like to do this so as not to construct any objects in the cross method. Using returned parameters (for example, void cross(const Vec3 &b, Vec3& out) would be possible, but ugly to read. I'm interested in updating an existing variable.

 #include <iostream> using namespace std; class Vec3{ public: Vec3(){ static int count = 0; id = count++; p = new float[3]; cout << "Constructor call "<<id <<" "<<p<< " "<<this<< endl; } ~Vec3(){ cout << "Deconstructor call "<<id << " "<<p<<" "<<this<< endl; delete[] p; } Vec3(Vec3&& other) : p(nullptr) { cout << "Move constructor call "<<id << " "<<p<<" "<<this<< endl; p = other.p; other.p = nullptr; } Vec3& operator=(Vec3&& other) { cout << "Move assignment operator call from "<<other.id<<" to "<<id << " "<<p<<" "<<this<< endl; if (this != &other) { p = other.p; other.p = nullptr; } return *this; } Vec3 cross(const Vec3 &b){ float ax = p[0], ay = p[1], az = p[2], bx = bp[0], by = bp[1], bz = bp[2]; Vec3 res; res.p[0] = ay * bz - az * by; res.p[1] = az * bx - ax * bz; res.p[2] = ax * by - ay * bx; return res; } float *p; int id; }; int main(int argc, const char * argv[]) { Vec3 a,b,c; a = b.cross(c); return 0; } 
+4
source share
4 answers

Another solution is to return the “expression object” from a.cross(b) , derivation of the calculation until such an object is assigned to c , and then in operator= you really do the calculation:

  struct Vec3 { CrossProduct cross(const Vec3& b); Vec3& operator=(CrossProduct cp) { do calculation here putting result in `*this` } } 

and add similar mechanisms for building, etc.

This is more active, but a number of C ++ math libraries use this design pattern.

+4
source

If you assign a new value directly:

 Vec3 a = b.cross(c); 

Then it is possible that the RVO will take effect, and there is no temporary build and carry over later. Make sure you compile with optimizations. The return value will be built in place at.

Also allocating an array of 3 floats on the heap is similar to a performance killer. Using a C-like array float p[3] or std::array<float, 3> should do much better.

+2
source

To update an existing variable, you can use the out parameter:

 // out parameter version void cross(const Vec3 &b, Vec3& res){ float ax = p[0], ay = p[1], az = p[2], bx = bp[0], by = bp[1], bz = bp[2]; res.p[0] = ay * bz - az * by; res.p[1] = az * bx - ax * bz; res.p[2] = ax * by - ay * bx; return res; } 

RVO will return to the constructor when the version of the return value will be used as an initializer ( but not when assigning an existing object ):

 // return value version (RVO) Vec3 cross(const Vec3& b) { Vec3 t; cross(b, t); return t; } 

You can also suggest a mutator of the result object:

 // assignment version void set_cross(const Vec3& a, const Vec3& b) { a.cross(b,*this); } 

All three member functions can coexist and reuse each other's code efficiently, as shown.

+1
source

This is not a direct answer to your question, and I can only make a small contribution, because currently the answers relate to important points, but I want to draw your attention to the shortcomings of the heap design you have chosen.

A 3D vector rarely comes alone.

This, of course, is a compromise depending on the number of moves that you will need to make and the number of operations performed on the vectors.

If you use only individual vectors and do a lot of copy / move, you can stick to your heap design. But if you have several vectors (for example, in a vector or an array), and you want to work with them, I would recommend you not to do heap allocation if you are worried about performance.

 std::vector<Vec3> a(20); class con_Vec3 { double x, y, z; }; std::vector<con_Vec3> b(20); 

Vector a will support a continuous block of pointers for swimming, where the float values, in turn, will be somewhere else in memory, which means that your values ​​are actually scattered across memory. Vector b opposite will contain an adjacent block of 60 double s, all in one place.

The cost of moving such a std::vector will be the same for both cases (quasi-swap each), but if you copy them, the heap-based solution will be slower, since 21 selection and 20 copies will be performed, while in the solution without heap there is one distribution and 20 copies covering the entire vector.

0
source

Source: https://habr.com/ru/post/1488267/


All Articles