我如何禁用c++返回值优化的一种类型
How can I disable c++ return value optimization for one type only?
我遇到过这样的情况:我确实需要在复制构造函数/赋值操作符中执行重要的代码。算法的正确性取决于它。
虽然我可以用编译器开关禁用返回值优化,但这似乎是一种浪费,因为它只是我需要禁用的一种类型,所以为什么整个应用程序的性能会受到影响?(更别提我的公司不允许我加开关了)。
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中无效。
任何想法?
编辑:请接受这是我能做我需要做的事情的唯一方法。我不能只是‘改变设计’。调用代码是固定的,我必须在数学运算符和复制构造函数中实现我的策略。赋值运算符。其想法是,在"a4 = (a1+a2)*a3/a1"方程中计算的中间值不能在程序中的其他任何地方引用-但a4可以。
在这里回答我自己的问题:我将咬紧牙关使用一个中间类型:
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&)构造函数中做"非常重要的事情"
正如Angew指出的那样,您可以使用中间类型。下面是一个使用move元素进行优化的示例。
#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
返回的临时变量,在整个复制初始化过程中持续(更准确地说:在整个完整表达式中,包括a4
的构造)。这就是为什么我们可以从B
中返回A const&
的原因,即使B
对象只是作为临时对象创建的。(如果我错了,请参阅我之前的一些其他解决方案的编辑。: D)
这个例子的本质是中间类型、移动因子和引用返回的组合。
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
——调用来使用操作符 的返回值
RVO是标准允许的,在以下情况下([class. class]§31,只列出适用的部件):
在具有类返回类型的函数的返回语句中,当表达式是非易失性自动对象的名称(其他)时而不是函数或catch子句参数)具有相同的cv-不合格类型作为函数的返回类型,可以进行复制/移动操作将自动对象直接构造到函数的返回值
当一个临时类对象没有绑定到引用(12.2)时,将被复制/移动到具有相同引用的类对象类型时,复制/移动操作可省略方法的目标直接构造临时对象省略了复制/移动
在你的代码中:
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;
有两个可执行的复制:将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&
。
- 在运行时检查继承是否只有一种类型和 void*
- 另一种类型的智能ptr,比如具有弱refs的unique_ptr
- void* 数组将元素转换为另一种类型
- 将一种类型的比特重新解释为不同类型的比特的技术
- 为什么需要类型名称,即使似乎足以推断名称应该是一种类型?
- 在 c++ 中将一种结构类型分配给另一种类型
- 将空基类优化对象强制转换为另一种类型是否会破坏严格的别名?
- 我怎样才能让编译器推导出一种类型的 nullptr
- 一种类型的多个 cv 分解
- 如何将对象定义为一种类型,然后再将其声明为子类型
- 类中的结构不是一种类型
- 一种类型特征,标识哪个类提供通过重载解析选择的函数
- 将 32 位浮点数和不强制转换的 32 位整数与双精度进行比较,当其中一个值可能太大而无法完全适合另一种类型时
- constexpr检查一种类型
- 何时与另一种类型兼容
- 依赖名称不是一种类型,带有 SFINAE 的模板
- 自动将一种 c++ 类型转换为另一种
- C 模板可以匹配(仅)一种类型的列表或另一种类型的列表
- 是否可以将一种类型的数组转换为大小不同的另一种类型的数组?
- 在比较中使用已强制转换为另一种类型的函数指针是否定义良好?