如何禁用仅针对一种类型的C++返回值优化?

问题描述:

我遇到了这样的情况,我需要在复制构造函数/赋值运算符中执行非平凡的代码,真的需要。算法的正确性取决于它。如何禁用仅针对一种类型的C++返回值优化?

虽然我可以用一个编译器开关禁用返回值优化,这似乎是一种浪费,因为它是唯一的一种类型,我需要它停用,所以为什么要整个应用程序的性能受苦? (无论如何,我的公司不会允许我添加开关)。

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将不会与拷贝构造函数来创建的,而“做真正重要的事情”不会发生!

我知道我可以在一个解决方案,其中操作者+返回不同的类型(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个move构造函数,但我不知道这会工作,我不就像它在C++ 03中无效。

任何想法?

编辑:请接受,这是我能做我需要做的唯一方法。我不能只是'改变设计'。调用代码是固定的,我必须在数学运算符内部实现我的策略并复制构造函数&赋值运算符。这个想法是,在“a4 =(a1 + a2)* a3/a1”方程中计算的中间值不能在程序中的其他任何地方被引用 - 但是a4可以。我知道这是模糊的,但你只需要忍受它。

+9

你能解释为什么你需要它吗?我认为我们会更好地说服你,你没有。 – 2013-04-26 10:41:52

+2

最好的想法似乎改变算法,以便它不依赖于复制对象的次数。 – 2013-04-26 10:42:40

+2

移动构造函数不会进入它。如果一个副本被删除,那么就没有移动的余地。我会说你最好重新设计代码。 – juanchopanza 2013-04-26 10:43:54

RVO由标准允许的,在下列情况下([class.copy]§31,上市仅适用部分):

  • return语句中

    在功能带班回报型,当表达是一种非挥发性的自动对象的具有相同的CV-不合格 类型作为函数返回类型名称(其他 比的函数或catch子句参数)时,复制/移动操作可以是 省略通过将自动对象直接构造成 函数的返回值

  • 当一个临时类对象尚未绑定到引用(12。2)将被复制/移动到具有相同 CV-非限定类型的一类对象中,复制/移动操作可以通过 直接构建临时对象到 省略副本的目标可以省略/移动

在您的代码:

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&

+0

这是我需要的第二个安全防范。如果返回类型更改为volatile,该怎么办? 'volatile A operator +(const A&,const A&)' – user2020792 2013-04-26 12:00:40

+0

@ user2020792这没有帮助。第二点(关于从临时对象复制的那个)不会为'volatile'造成异常。你唯一的希望就是可以有一个返回值的中间类型。 – Angew 2013-04-26 12:03:58

正如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建设)。 这就是为什么我们可以从B之内返回A const&,即使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 - 调用使用操作
  • 的返回值
+0

谢谢,但我现在宁愿将解决方案保留在C++ 03中。 – user2020792 2013-04-26 13:46:21

+0

@ user2020792下次请用“C++ 03”标记您的问题。 C++ 03是一个被弃用的标准,已被C++ 11所取代,尽管我承认大多数编译器还没有完全支持后者。 – dyp 2013-04-26 13:51:53

+0

我没有在我的问题中明确地声明C++ 03,只是不在标题中。 – user2020792 2013-04-26 21:55:50

这里回答我的问题:我要硬着头皮使用中间类型:

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(常量乙&)构造了 “非常重要的事情”。

+1

也许你需要[Expression Template](http://en.wikipedia.org/wiki/Expression_templates)模式。其目的是消除大型临时对象的创建。例如,如果'u'和'v'是相同大小的长向量,并且'a'是一个double,则可以创建一个值为'a *(uv)'的向量,但不会为' uv'。 – 2013-04-26 18:23:52