我遇到过真的需要在copy-constructor / assignment-operator中执行非平凡代码的情况。算法的正确性取决于它。
虽然我可以使用编译器开关禁用返回值优化,但这似乎是浪费,因为它只是我需要它禁用的一种类型,那么为什么整个应用程序的性能会受到影响? (更不用说我公司不允许我添加开关了。)
struct A {
explicit A(double val) : m_val(val) {}
A(const A& other) : m_val(other.m_val) {
// Do something really important here
}
A& operator=(const A& other) {
if (&other != this) {
m_val = other.m_val;
// Do something really important here
}
return *this;
}
double m_val;
};
A operator+(const A& a1, const A& a2) {
A retVal(a1.m_val + a2.m_val);
// Do something else important
return retVal;
}
// Implement other operators like *,+,-,/ etc.
这个类将被用作:
A a1(3), a2(4), a3(5);
A a4 = (a1 + a2) * a3 / a1;
返回值优化意味着不会使用复制构造函数创建a4,并且“真正重要的事情”不会发生!
我知道我可以破解一个解决方案,其中operator +返回一个不同的类型(B,比方说)并且有一个A构造函数,它将B作为输入。但随后需要实施的运营商数量爆炸式增长:
B operator+(const A& a1, const A& a2);
B operator+(const B& a1, const A& a2);
B operator+(const A& a1, const B& a2);
B operator+(const B& a1, const B& a2);
必须有更好的解决方案。我怎么能破解它以便RVO不会发生在我的类型上?我只能更改A类代码和运算符。我无法更改呼叫站点代码;即我不能这样做:
A a1(3), a2(4), a3(5);
A a4;
a4 = (a1 + a2) * a3 / a1;
我考虑过的一件事是尝试尝试使用C ++ 11移动构造函数,但我不确定这是否可行,并且我不喜欢它在C ++ 03中无效。< / p>
有什么想法吗?
编辑:请接受这是我能做我需要做的唯一方法。我不能只是'改变设计'。调用代码是固定的,我必须在数学运算符和复制构造函数&amp;中实现我的策略。赋值运算符。这个想法是在“a4 =(a1 + a2)* a3 / a1”等式中计算的中间值不能在程序中的任何其他位置引用 - 但是a4可以。我知道这很模糊,但你只需忍受它。
答案 0 :(得分:2)
在这里回答我自己的问题:我要咬紧牙关并使用中间类型:
struct B;
struct A
{
A(int i) : m_i(i) {}
A(const B& a);
A(const A& a) : m_i(a.m_i)
{
std::cout << "A(const A&)" << std::endl;
}
int m_i;
};
struct B
{
B(int i) : m_i(i) {}
int m_i;
};
A::A(const B& a) : m_i(a.m_i)
{
std::cout << "A(const B&)" << std::endl;
}
B operator+(const A& a0, const A& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "A+A" << std::endl;
return b;
}
B operator+(const B& a0, const A& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "B+A" << std::endl;
return b;
}
B operator+(const A& a0, const B& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "A+B" << std::endl;
return b;
}
B operator+(const B& a0, const B& a1)
{
B b(a0.m_i + a1.m_i);
std::cout << "B+B" << std::endl;
return b;
}
int main()
{
A a(1);
A b(2);
A c(3);
A d = (a+b) + (a + b + c);
}
GCC 4.2.1上的输出:
A+A
B+A
A+A
B+B
A(const B&)
我可以在A(const B&amp;)构造函数中做“非常重要的事情”。
答案 1 :(得分:1)
正如Angew所指出的,你可以使用中间类型。这是使用move ctor进行一些优化的示例。
#include <utility>
#include <iostream>
struct B;
struct A {
explicit A(double val) : m_val(val)
{
std::cout << "A(double)" << std::endl;
}
A(A&& p) : m_val(p.m_val)
{ /* no output */ }
A(const A& other) : m_val(other.m_val) {
// Do something really important here
std::cout << "A(A const&)" << std::endl;
}
A& operator=(const A& other) {
if (&other != this) {
m_val = other.m_val;
// Do something really important here
std::cout << "A::operator=(A const&)" << std::endl;
}
return *this;
}
double m_val;
A(B&&);
};
struct B
{
operator A const&() const
{
std::cout << "B::operator A const&()" << std::endl;
return a;
}
private:
friend struct A;
A a;
// better: befriend a factory function
friend B operator+(const A&, const A&);
friend B operator*(const A&, const A&);
friend B operator/(const A&, const A&);
B(A&& p) : a( std::move(p) )
{ /* no output */ }
};
A::A(B&& p) : A( std::move(p.a) )
{
std::cout << "A(B&&)" << std::endl;
}
B operator+(const A& a1, const A& a2) {
std::cout << "A const& + A const&" << std::endl;
A retVal(a1.m_val + a2.m_val);
// Do something else important
return std::move(retVal);
}
B operator*(const A& a1, const A& a2) {
std::cout << "A const& * A const&" << std::endl;
A retVal(a1.m_val * a2.m_val);
// Do something else important
return std::move(retVal);
}
B operator/(const A& a1, const A& a2) {
std::cout << "A const& / A const&" << std::endl;
A retVal(a1.m_val / a2.m_val);
// Do something else important
return std::move(retVal);
}
int main()
{
A a1(3), a2(4), a3(5);
A a4 = (a1 + a2) * a3 / a1;
}
IIRC,由a1 + a2
返回的临时文件持续进行整个复制初始化(更确切地说:对于整个完整表达式,包括AFAIK构造a4
)。
这就是我们可以从A const&
内返回B
的原因,即使B
个对象仅作为临时对象创建。
(如果我错了,请参阅我以前编辑的其他解决方案..:D)
这个例子的本质是中间类型,移动ctors和所述参考返回的组合。
g ++ 4.6.3和clang ++ 3.2的输出:
A(double) <---- A a1(3);
A(double) <---- A a2(4);
A(double) <---- A a3(5);
A const& + A const& <---- a1 + a2;
A(double) <-- A retVal(a1.m_val + a2.m_val);
B::operator A const&()<---- __temp__ conversion B --> const A&
A const& * A const& <---- __temp__ * a3;
A(double) <-- A retVal(a1.m_val * a2.m_val);
B::operator A const&()<---- __temp__ conversion B --> const A&
A const& / A const& <---- __temp__ / a1;
A(double) <-- A retVal(a1.m_val / a2.m_val);
A(B&&) <---- A a4 = __temp__;
现在复制和移动操作(未显示)被拆分,我认为你可以更准确地实现你所属的“重要的东西”:
A(double)
- 从数值A
对象
A(A const&)
- A
对象的实际副本;不会发生在这里A(B&&)
- 从运营商结果构建A
对象B(A&&)
- 为运算符的返回值调用B::operator A const&() const
- 调用以使用运算符的返回值答案 2 :(得分:0)
标准允许RVO,在以下情况下([class.copy]§31,仅列出适用的部分):
在具有类返回类型的函数的return语句中,当表达式是非易失性自动对象的名称时(其他 比函数或catch子句参数)具有相同的cv-unqualified 键入函数返回类型,复制/移动操作即可 通过直接构造自动对象省略 函数的返回值
当一个尚未绑定到引用(12.2)的临时类对象被复制/移动到具有相同的类对象时 cv-unqualified类型,可以省略复制/移动操作 将临时对象直接构造到目标中 省略复制/移动
在您的代码中:
A operator+(const A& a1, const A& a2) {
A retVal(a1.m_val + a2.m_val);
// Do something else important
return retVal;
}
A a4 = (a1 + a2) * a3 / a1;
涉及两个elidable副本:将revVal
复制到存储operator+
返回值的临时对象中,并将此临时对象复制到a4
。
我无法看到防止第二份副本(从返回值到a4
)的方法,但标准的“非易失性”部分让我相信这应该可以防止第一个副本:
A operator+(const A& a1, const A& a2) {
A retVal(a1.m_val + a2.m_val);
// Do something else important
volatile A volRetVal(retVal);
return volRetVal;
}
当然,这意味着您必须为A
const volatile A&
定义一个额外的副本构造函数。