让所有 setter 函数返回对 c++ 中对象的引用是否很好?
8 回答
如果需要在对象上设置很多东西,这是一个足够可用的模式。
class Foo
{
int x, y, z;
public:
Foo &SetX(int x_) { x = x_; return *this; }
Foo &SetY(int y_) { y = y_; return *this; }
Foo &SetZ(int z_) { z = z_; return *this; }
};
int main()
{
Foo foo;
foo.SetX(1).SetY(2).SetZ(3);
}
此模式替换了一个采用三个整数的构造函数:
int main()
{
Foo foo(1, 2, 3); // Less self-explanatory than the above version.
}
如果您有许多并不总是需要设置的值,这将很有用。
作为参考,此类技术的更完整示例在 C++ FAQ Lite 中称为“命名参数惯用语”。
当然,如果您将其用于命名参数,您可能需要查看boost::parameter。或者你可能不会...
this
如果您想将 setter 函数调用链接在一起,您可以返回一个引用,如下所示:
obj.SetCount(10).SetName("Bob").SetColor(0x223344).SetWidth(35);
我个人认为代码比替代代码更难阅读:
obj.SetCount(10);
obj.SetName("Bob");
obj.SetColor(0x223344);
obj.SetWidth(35);
这种风格的典型目的是用于对象构造。
Person* pPerson = &(new Person())->setAge(34).setId(55).setName("Jack");
代替
Person* pPerson = new Person( 34, 55, "Jack" );
如果使用第二种更传统的样式,可能会忘记传递给构造函数的第一个值是年龄还是 id?这也可能导致基于某些属性的有效性的多个构造函数。
使用第一种样式可能会忘记设置一些对象属性,并且可能会导致对象未“完全”构造的错误。(稍后会添加一个类属性,但并非所有构造位置都已更新以调用所需的设置器。)
随着代码的发展,我真的很喜欢这样一个事实,即在更改构造函数的签名时,我可以使用编译器来帮助我找到创建对象的所有位置。所以出于这个原因,我更喜欢使用常规的 C++ 构造函数而不是这种风格。
这种模式可能适用于根据与许多数据库应用程序中使用的规则类似的规则随时间维护其数据模型的应用程序:
- 您可以将字段/属性添加到默认为 NULL 的表/类中。(因此升级现有数据只需要在数据库中添加一个新的 NULL 列。)
- 未更改的代码在添加了此 NULL 字段后仍应正常工作。
不是所有的 setter,但其中一些可以返回对 object 的引用以很有用。
有点儿
a.SetValues(object)(2)(3)(5)("Hello")(1.4);
我很久以前用它来构建处理所有转义问题和其他事情的 SQL 表达式构建器。
SqlBuilder builder;
builder.select( column1 )( column2 )( column3 ).
where( "=" )( column1, value1 )
( column2, value2 ).
where( ">" )( column3, 100 ).
from( table1 )( "table2" )( "table3" );
我无法在 10 分钟内复制资源。所以实施是在幕后。
如果您的动机与链接有关(例如 Brian Ensink 的建议),我将提供两条评论:
1. 如果你发现自己经常同时设置很多东西,那可能意味着你应该生成一个struct
orclass
来保存所有这些设置,以便它们可以同时传递。下一步可能是使用它struct
或class
对象本身......但由于您使用的是 getter 和 setter,因此如何在内部表示它的决定无论如何对类的用户都是透明的,所以这个决定将涉及更多类比什么都复杂。
2. setter 的一种替代方法是创建一个新对象,对其进行更改并返回它。这在大多数类型中既低效又不合适,尤其是可变类型。然而,尽管它在许多语言的字符串类中使用,但人们有时会忘记它。
此技术用于命名参数 Idiom中。
IMO setter 是一种代码气味,通常表明以下两种情况之一:
用鼹鼠山造山
如果你有这样的课程:
class Gizmo
{
public:
void setA(int a) { a_ = a; }
int getA() const { return a_; }
void setB(const std::string & b) { v_ = b; }
std::string getB() const { return b_; }
private:
std::string b_;
int a_;
};
...而且这些值真的就是这么简单,那为什么不把数据成员公开呢?:
class Gizmo
{
public:
std::string b_;
int a_;
};
...简单得多,如果数据如此简单,您将一无所获。
另一种可能性是你可能是
把山变成鼹鼠山
很多时候数据并不那么简单:也许您必须更改多个值,进行一些计算,通知其他对象;谁知道呢。但是,如果数据足够重要以至于您确实需要 setter 和 getter,那么它也不够重要以至于需要错误处理。所以在这些情况下,你的 getter 和 setter 应该返回某种错误代码或做其他事情来表明发生了不好的事情。
如果您像这样将调用链接在一起:
A.doA().doB().doC();
...并且 doA() 失败了,你真的想调用 doB() 和 doC() 吗?我对此表示怀疑。
我不这么认为。通常,您认为“setter”对象就是这样做的。
此外,如果你只是设置对象,你没有指向它的指针吗?