使用引用作为返回值被认为是不好的编码风格吗?

3

我有一个关于设置值的问题:

假设我有一个想要更改的类字符串。我正在使用以下代码:

void setfunc(std::string& st) { this->str = st; }

还有一个功能,可以使用字符串引用而不是空值来设置值:

std::string& reffunc() { return this->str; }

现在,如果我要设置一个值,我可以使用:

现在如果我要设置一个值,我可以使用:

std::string text("mytext");
setfunc(text);
//or
reffunc() = text;

现在我的问题是,如果使用第二种设置值的形式被认为是不好的。但实际上,这种方式没有性能差异。

1
在类成员函数中,您不需要使用 this-> - πάντα ῥεῖ
2
this 使事情更加明确,消除歧义是一个好的做法。 - Jean-Baptiste Yunès
3
在第二种情况下,你可以将str声明为公共的,这样也是一样的。 - Bo Persson
1
@Jean-BaptisteYunès:是的和不是。你必须在利用这个优势的同时避免啰嗦冗长。 - Christian Hackl
1
@Jean-BaptisteYunès 我不同意。为了消除参数名称的歧义,我通常只在成员或参数符号上使用后缀“_”(更短)。 - πάντα ῥεῖ
显示剩余5条评论
4个回答

3
首先,类中有getter和setter的原因在于它可以保护其不变量并且更易于修改。如果只有返回值为值的setter和getter,则该类具有以下自由度,而无需破坏API:
- 更改内部表示方式。也许字符串以更适合内部操作的不同格式存储。也可能没有存储在类本身中。 - 验证传入值。字符串是否具有最大或最小长度?setter可以强制执行此内容。 - 保留不变式。如果字符串更改,则类的第二个成员需要更改吗?setter可以执行更改。也许字符串是一个URL,类缓存某些关于它的信息。setter可以清除缓存。
如果将getter更改为返回const引用(有时这样做是为了节省副本),则会丢失一些表示自由度。现在您需要一个实际的返回类型对象,您可以引用它,并且它已经存在足够长的时间。您需要为返回值添加生命周期保证,例如承诺在使用非const成员之前,引用未被使无效。您仍然可以返回对类的直接成员不是直接成员的对象的引用(例如返回对内部名称结构的第一个名称部分的引用),或者取消引用的指针。
但是,如果通过非const引用返回,则几乎所有赌注都关闭了。由于客户端可以更改引用的值,因此您不能再依赖于调用setter并执行受类控制的代码时更改值。您无法约束该值,也无法保留不变式。通过非const引用返回使类与具有公共成员的简单结构几乎没有区别。
这就导致了最后一个选项,即将数据成员直接公开。与返回非const引用的getter相比,唯一失去的是返回的对象不能是间接成员;它必须是直接的真实成员。
在该方程的另一侧是性能和代码开销。每个getter和setter都是要编写的附加代码,并提供了额外的出错机会(曾经复制粘贴某个成员的getter / setter对来创建同样的另一个成员,然后忘记在其中更改其中一个变量吗?)。编译器可能会内联访问器,但如果不内联,则会产生调用开销。如果返回像字符串之类的东西,则必须进行复制,这很昂贵。
这是一个权衡,但大多数情况下最好编写访问器,因为它为您提供了更好的封装和灵活性。

2
我们无法看到成员变量 str 的定义。如果它是私有的,你的 reffunc() 暴露了一个私有成员的引用;你正在编写一个违反设计的函数,所以你应该重新考虑你正在做什么。 此外,这是一个引用,因此在使用该引用时必须确保包含 str 的对象仍然存在。 此外,你正在展示外部实现细节,这些细节在未来可能会发生变化,从而改变接口本身(如果 str 变成不同的东西,则可以适应 setfunc() 的实现,reffunc() 的签名必须更改)。
你写的并没有错,但可能会被错误使用。你正在减少封装性。这是一个设计选择。

1

没问题。不过,你需要注意以下这些陷阱:

  • 所引用的对象是可修改的。 当你返回一个非const引用时,你暴露了没有保护措施的数据。显然,但还是要注意!

  • 所引用的对象可能超出作用域。 如果所引用的对象生命周期结束,访问该引用将导致未定义行为。然而,它们可以用来扩展临时变量的生命周期。


如果您在函数内定义对象,则必须小心。返回的引用可能无效。对于std::string,它可以工作,因为其复制构造函数是明确定义的,并且可以将函数中std::string对象中找到的内容传递给返回值。 - BeschBesch

1
你使用 reffunc() 函数的方式被认为是不好的编程方式。但一般来说,返回引用并不是不好的编程方式(正如评论中所提到的)。
以下是为什么 reffunc() = text; 被认为是不好的编程方式:
人们通常不希望在赋值语句的左侧看到函数调用,而是在右侧。当看到函数调用时,自然的期望是它计算并返回一个值(或 rvalue,在赋值语句的右侧),而不是一个引用(或 lvalue,在赋值语句的左侧)。
因此,将函数调用放在赋值语句的左侧会使代码变得更加复杂,因此可读性较差。请记住,如果没有其他动机(正如你所说,性能在这些情况下通常相同),良好的编程建议使用“set”函数。
阅读优秀书籍 "Clean Code" 以了解更多有关清晰编程的问题。
关于在函数中返回引用的问题,这是您提出的问题标题,这并不总是不良编码,有时为了使代码更加简洁和简明而必要。特别是在C++中许多运算符重载特性需要返回引用才能正常工作(例如std::vector中的operator[]和赋值运算符),通常可以帮助代码更易读和减少复杂性(请参见注释)。

你在回答描述中的问题,但没有回答标题中更广泛的问题。我建议更新答案以反映这一点,或提示提问者将标题更改为“使用函数返回引用设置值是不好的做法吗?”。 - hauron
你没有指定哪个是“糟糕编码”中的setfuncreffunc。根据答案,我假设你指的是reffunc。哪个更糟糕是一个主观问题,这就是这种类型问题的问题所在。为了反驳这个观点,请看看std::vector。它提供了reference operator[] (size_type pos)const reference operator[] (size_type pos) constreference at (size_type pos)const reference at (size_type pos) const,但它不提供void set(size_type pos, const value_type& val) - David Hammen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接