使用成员函数更改对象或返回并分配该对象是否被认为是更好的做法?

时间:2019-06-16 04:31:37

标签: c++

首先,很抱歉,如果问题措辞不佳,我不确定如何最好地表达它。另外我还要注意,我编写的代码旨在供他人使用,因此可读性和易用性是关键。

比方说,我有一个类vec3,其中包含一个称为normalize的成员函数。对于如何操作此功能,我看到两个选项。我可以使用该函数对对象本身中包含的数据进行规范化,也可以在函数中创建新的规范化vec3并将其返回。

现在显然这两个功能都有其用途,但是我想知道的是,对于这种事情是否有标准的做法。显然,返回新对象更加灵活,因为我总是可以将新对象分配给该对象:

v = v.normalize();

vec3 vec3::normalize(){
    vec3 r = *this;
    double s = sqrt(r.x*r.x + r.y*r.y + r.z*r.z);
    if(s<1e-8){
        r.x=0.0; r.y=0.0; r.z=0.0;
    }
    else{
        x /= s;
        y /= s;
        z /= s;        
    }
    return r;

}

现在也许现代的编译器可能会对此进行优化,但是如果我几乎总是以这种方式使用此函数,那么仅更改函数中的对象似乎更加有效。

v.normalize()

void vec3::normalize(){
    double s = sqrt(x*x + y*y + z*z);
    if(s<1e-8){
        x=0.0; y=0.0; z=0.0;
    }
    else{
        x /= s;
        y /= s;
        z /= s;        
    }
}

现在我可以创建两个不同的函数,一个返回一个值,一个改变值,但是对每个函数执行此操作似乎有点过头了,而且我不确定如何为用户区分它们。

现在显然这些问题很多都可能归结为程序员的个人喜好,但是如果有解决此类问题的标准方法,我将不胜感激一些技巧。

谢谢。

2 个答案:

答案 0 :(得分:3)

从广义上讲,如果一个函数是成员函数,并且以这样一种方式命名,即建议一个使对象变异的操作,那么它可能会/应该这样做。 vector::clear不返回空向量;清除当前向量。

如果向量类型上有一个normalize函数,我会说人们会合理地假设它正在对向量本身进行规范化。

现在,这几乎不是普遍的。实际上,仅标准库就不是普遍适用。 basic_string::substr不会将字符串转换为其自身的子集;它会返回一个新字符串,该字符串是原始字符串的子集。

因此,通常这将是经销商的选择。但这并不意味着您的类的读者不会做任何假设,而一般的假设是看起来像他们在变异对象的成员函数可能会做。 a = a.normalize()看起来有点怪。

相反,如果normalize是一个非成员函数,则可以合理地假设它返回的是归一化向量,而不是就地归一化向量。原因是,如果它在原位突变,您将无法执行以下操作:

normalize(a + b);

人们通常会认为这会起作用。但是,如果它是变异的,它不会。它将返回一个对临时变量的引用,该临时变量将在表达式的末尾销毁。

相比之下,(a + b).normalize()尽管在技术上是可行的,但看起来确实很奇怪,并且类似的东西几乎总是有代码味道。除非稍后在同一表达式中使用该向量,否则您将获得对 destroyed 对象的引用。

那很糟糕。而且经验丰富的C ++程序员都知道,在几乎每种情况下,看起来像这样的代码(在临时实例上调用成员函数)都是在做这坏事。

因此,如果要使用可变成员函数,请牢记两件事:使(a + b).normalize()编译失败,并在最后返回*this,以便用户可以链接根据需要起作用。例如:

vec3 &vec3::normalize() & //Prevents the use of this function on prvalues.
{
    double s = sqrt(x*x + y*y + z*z);
    if(s<1e-8){
        x=0.0; y=0.0; z=0.0;
    }
    else{
        x /= s;
        y /= s;
        z /= s;        
    }
    return *this;
}

答案 1 :(得分:1)

类的接口应该是什么?它完全取决于类及其代表的内容。
例如,如果我考虑您的函数vec3::normalize(),我会说normalize()不应修改对象,因为它不接受用户的任何输入,并且可以在构造函数本身中完成此归一化。
< br /> 现在考虑一种情况,如果有人多次调用normalize()函数,那么会发生什么?您想处理这种情况吗?这意味着normalize()函数应该返回一个新对象,而不是修改对象。

让我们考虑QRect的{​​{1}}类,它提供了两个功能

Qt执行,”返回归一化的矩形;即,矩形的宽度和高度均为非负数。 如果width()<0,则函数交换左和右角,如果height()<0,则交换上角和下角。“ 该方法返回一个新对象。

第二种方法QRect QRect::normalized() const,它执行“分别将dx1,dy1,dx2和dy2添加到矩形的现有坐标中。”

因此,从这两个函数中,我可以得出结论:如果我想对对象执行一些逻辑并希望保留更改,那么我将寻求修改对象的函数,并且如果我想对对象执行一些逻辑并且不想在它的任何修改(对象状态不应该改变),然后我将从返回新对象的功能出发。