30 May 2022

C++ 核心指南目录

F.20: For “ out ” output values, prefer return values to output parameters

理由

返回值类型声明还有代码文档化的作用,即在代码中就能说明函数的输出值类型。然而,一个带&符号的参数可以是一个入参,也同时可以是出参,这样就很容易造成误解了。

此指南也适用于标准容器这类大对象,在返回结果的过程中,为了保证执行性能,标准容器类用了隐式的移动操作,减少了显式手动的内存管理代码。

如果有多个值需要返回,可以用元组( tupple )或类似的多成员类型。

例子

// OK: return pointers to elements with the value x
vector<const int*> find_all_ok(const vector<int>& v, int x) {
    auto rv = vector<const int*>();
    for(vector<int>::const_iterator it=v.begin(); it!=v.end(); it++) {
        if (*it == x) {
            rv.push_back(&*it);
        }
    }
    return rv;
}

int main()
{
    auto vv = vector<int>{1,2,3,4};
    auto vvv = find_all_ok(vv, 3);
    for(int i=0; i<vvv.size(); i++) {
        cout << *vvv[i];
    }
    cout << endl;
}
3
// Bad: place pointers to elements with value x in-out
void find_all_bad(const vector<int>& v, vector<const int*>& out, int x)
{
    for(vector<int>::const_iterator it=v.begin(); it!=v.end(); it++) {
        if (*it == x) {
            out.push_back(&*it);
        }
    }
}
int main()
{
    auto vv = vector<int>{1,2,3,4};
    auto vvv = vector<const int*>();
    find_all_bad(vv, vvv, 3);
    for(int i=0; i<vvv.size(); i++) {
        cout << *vvv[i];
    }
}
3

注意

被移动的结构体中含有多个小对象的话,性能可能较差。

例外

  • 通过unique_ptr或者shared_ptr返回还没具体化的类型。所谓还没有具体化的类型指的是类层级结构中间的那些类型。
  • 如果某个类型的移动成本很高,比如array<BigTrivial>,考虑在自由存储区分配内存,然后通过unique_ptr返回一个句柄。或者通过一个读写引用参数作为出入参。
  • 如果要利用某个容器类持续保持一些列对象给多个函数进行处理的话,可以通过读写引用作为出入参传给各个函数。

例子

假设 Matrix 类有移动操作:

Matrix operator+(const Matrix& a, const Matrix& b)
{
    Matrix res;
    // ... fill res with the sum ...
    return res;
}

Matrix x = m1 + m2;  // move constructor

y = m3 + m3;         // move assignment

注意

返回值优化过程不处理赋值情况,但是会优化移动赋值的过程。

struct Package {      // exceptional case: expensive-to-move object
    char header[16];
    char load[2024 - 16];
};

Package fill();       // Bad: large return value
void fill(Package&);  // OK

int val();            // OK
void val(int&);       // Bad: Is val reading its argument

注意

不建议返回 const 值。返回 const 值没啥价值,并且对移动操作有影响。

// bad: that "const" is more trouble than it is worth
const vector<int> fct() {
    return vector<int>{1,2,3};
}
void g(vector<int>& vx)
{
    // ...
    fct() = vx;   // prevented by the "const"
    // ...
    vx = fct(); // expensive copy: move semantics suppressed by the "const"
    // ...
}

返回值设置成 const 是为了避免不小心访问临时数据(很少发生)。添加 const 阻止移动机制,从而导致性能变差(经常发生)。

强化

  • const 参数,改写之前没有读取操作,其实可以用低成本的返回值的方式。标注告警。
  • 返回一个常量值。去除 const ,返回一个非 const 值。