首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何仅对一种类型禁用c++返回值优化?

如何仅对一种类型禁用c++返回值优化?
EN

Stack Overflow用户
提问于 2013-04-26 10:32:21
回答 3查看 1.3K关注 0票数 1

我遇到了这样的情况:我确实需要在复制构造函数/赋值操作符中执行非平凡的代码。该算法的正确性取决于此。

虽然我可以使用编译器开关禁用返回值优化,但这似乎是一种浪费,因为它只是我需要禁用的一种类型,那么为什么整个应用程序的性能会受到影响呢?(更别提我的公司不允许我添加开关了)。

代码语言:javascript
复制
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.

这个类将被用作:

代码语言:javascript
复制
A a1(3), a2(4), a3(5);
A a4 = (a1 + a2) * a3 / a1;

返回值优化意味着不会使用复制构造函数创建a4,而且“真正重要的事情”不会发生!

我知道我可以黑进一个解决方案,其中operator+返回一个不同的类型(例如B),并有一个A构造函数,该构造函数以B作为输入。但随后,需要实施的运营商数量激增:

代码语言:javascript
复制
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类代码和操作符。我不能更改呼叫站点代码;也就是说,我不能这样做:

代码语言:javascript
复制
A a1(3), a2(4), a3(5);
A a4;
a4 = (a1 + a2) * a3 / a1;

我考虑尝试的一件事是尝试并尝试使用C++11移动构造函数,但我不确定这是否有效,而且我不喜欢它在C++03中无效。

有什么想法吗?

编辑:请接受这是我唯一能做我需要做的事情的方法。我不能就这么“改变设计”。调用代码是固定的,我必须在数学操作符和复制构造函数&赋值操作符中实现我的策略。其思想是,在"a4 = (a1+a2)*a3/a1“方程中计算的中间值不能在程序中的任何其他地方引用,但a4可以引用。我知道这很模糊,但你得接受它。

EN

回答 3

Stack Overflow用户

发布于 2013-04-26 13:44:19

在这里回答我自己的问题:我要咬紧牙关,使用中间类型:

代码语言:javascript
复制
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:

代码语言:javascript
复制
A+A
B+A
A+A
B+B
A(const B&)

我可以在A(const &)构造函数中做“非常重要的事情”。

票数 2
EN

Stack Overflow用户

发布于 2013-04-26 12:42:04

正如Angew所指出的,您可以使用中间类型。下面是一个使用move进行优化的示例。

代码语言:javascript
复制
#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的输出:

代码语言:javascript
复制
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 --调用以使用运算符的返回值
票数 1
EN

Stack Overflow用户

发布于 2013-04-26 11:45:54

标准允许使用RVO,在下列情况下(class.copy§31,只列出适用的部分):

  • 在具有类返回类型的函数中的返回语句中,当表达式是具有与函数返回类型相同的cv-不限定类型的非易失性自动对象(函数或catch-子句参数除外)的名称时,可以通过将自动对象直接构造到函数的返回值来省略复制/移动操作。
  • 当没有绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv非限定类型的类对象时,可以通过将临时对象直接构造到省略复制/移动的目标中来省略复制/移动操作。

在您的代码中:

代码语言:javascript
复制
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)的省略,但是标准的“非易失性”部分让我相信这应该会防止第一个副本的省略:

代码语言:javascript
复制
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
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16234323

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档