Optimizing the number of constructor calls

徘徊边缘 提交于 2019-12-21 07:50:51

问题


At work we have a class with an expensive constructor so we would like it to be called as few times as possible. We looked through the uses of it and tried to make the code more RVO friendly so to say.

However we found a quirk in the g++ compiler where we didn't understand what happened.

Please consider the two implementations of operator+

const Imaginary Imaginary::operator+(const Imaginary& rhs) const
{
    Imaginary tmp(*this);
    tmp.append(rhs);
    return tmp;
}

and

const Imaginary Imaginary::operator+(const Imaginary& rhs) const
{
    return Imaginary(*this).append(rhs);
}

I have put print-outs in the the various constructors and with the following little program

int main(int argc, char* argv[])
{
    Imaginary x(1, 1);
    Imaginary y(2, 1);

    Imaginary c = x + y;
    return 0;
}

I get this print out with the first implementation of operator+

int/int ctor
int/int ctor
Copy ctor

And I get the following when the second variant of operator+ is in use

int/int ctor
int/int ctor
Copy ctor
Copy ctor

Here we see that g++ is able to optimize away one call to the copy constructor in one case but not the latter and to my surprise, it managed to do it with the more clumsy implementation where I saved it to a temporary.

Now I could've understood it more if it was the other way around but appearantly it isn't and now I am hoping that maybe one you could enlighten me on this subject.

I should probably add that when we add --no-elide-constructors as a flag to g++ I get the following print out

int/int ctor
int/int ctor
Copy ctor
Copy ctor
Copy ctor

Regards, Mattias


回答1:


If the compiler cannot inline append, then it cannot determine that the return value is target object. Then it doesn't know that the temporary is being returned, and cannot construct it in place.

You would have the same behavior with:

Imaginary tmp(*this);
return tmp.append(rhs);

If the return value of append is opaque to the compiler (defined in another compilation unit), it prevents the optimization.




回答2:


Stephan T. Lavavej mentions in http://channel9.msdn.com/Events/GoingNative/2013/Don-t-Help-the-Compiler that (N)RVO only happens when the type of the returned value is exactly the same as the type returned from the method.

For example:

string foo() {string tmp; return tmp;}  // Same type, uses NRVO or automatic move.
string foo() {const string& tmp = "bar"; return tmp;}  // Types differ, no NRVO, nor automatic move.
string foo() {string tmp; string& ref = tmp; return ref;}  // Types differ, no NRVO, nor automatic move.
string foo() {string tmp; return (string&) tmp;}  // Types differ, no NRVO, nor automatic move.

(cf. http://coliru.stacked-crooked.com/a/79e79e5bb0350584)

I guess append returns a reference to Imaginary, and as Imaginary& is not of the same type as Imaginary this prevents (N)RVO.



来源:https://stackoverflow.com/questions/7320520/optimizing-the-number-of-constructor-calls

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!