C++:复制构造函数:使用 getter 还是直接访问成员变量?
我有一个带有复制构造函数的简单容器类。
您建议使用 getter 和 setter,还是直接访问成员变量?
public Container
{
public:
Container() {}
Container(const Container& cont) //option 1
{
SetMyString(cont.GetMyString());
}
//OR
Container(const Container& cont) //option 2
{
m_str1 = cont.m_str1;
}
public string GetMyString() { return m_str1;}
public void SetMyString(string str) { m_str1 = str;}
private:
string m_str1;
}
- 在示例中,所有代码都是内联代码,但在我们的实际代码中没有内联代码。
更新(2009 年 9 月 29 日):
其中一些答案写得很好,但它们似乎忽略了这个问题的要点:
这是讨论使用 getter/setter 与变量的简单设计示例< /p>
初始化列表或私有验证器函数并不是这个问题的真正一部分。我想知道这两种设计是否会使代码更易于维护和扩展。
有些人关注此示例中的字符串,但它只是一个示例,想象一下它是一个不同的对象。
我不关心性能。我们不在 PDP-11 上编程
I have a simple container class with a copy constructor.
Do you recommend using getters and setters, or accessing the member variables directly?
public Container
{
public:
Container() {}
Container(const Container& cont) //option 1
{
SetMyString(cont.GetMyString());
}
//OR
Container(const Container& cont) //option 2
{
m_str1 = cont.m_str1;
}
public string GetMyString() { return m_str1;}
public void SetMyString(string str) { m_str1 = str;}
private:
string m_str1;
}
- In the example, all code is inline, but in our real code there is no inline code.
Update (29 Sept 09):
Some of these answers are well written however they seem to get missing the point of this question:
this is simple contrived example to discuss using getters/setters vs variables
initializer lists or private validator functions are not really part of this question. I'm wondering if either design will make the code easier to maintain and expand.
Some ppl are focusing on the string in this example however it is just an example, imagine it is a different object instead.
I'm not concerned about performance. we're not programming on the PDP-11
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
为什么你需要 getter 和 setter?
简单:) - 它们保留不变量 - 即保证你的类,例如“MyString 总是有偶数个字符”。
如果按预期实现,您的对象始终处于有效状态 - 因此成员复制可以很好地直接复制成员,而不必担心破坏任何保证。通过另一轮状态验证传递已经验证的状态没有任何优势。
正如 AraK 所说,最好的方法是使用初始化列表。
没那么简单(1):
使用 getter/setter 的另一个原因是不依赖于实现细节。对于复制 CTor 来说,这是一个奇怪的想法,当更改此类实现细节时,您几乎总是需要调整 CDA。
没那么简单(2):
为了证明我是错的,您可以构造依赖于实例本身或其他外部因素的不变量。一个(非常做作的)示例:“如果实例数是偶数,则字符串长度是偶数,否则是奇数。”在这种情况下,复制 CTor 将不得不抛出或调整字符串。在这种情况下,使用 setter/getter 可能会有所帮助 - 但这不是一般情况。你不应该从奇怪的事情中得出一般规则。
Why do you need getters and setters at all?
Simple :) - They preserve invariants - i.e. guarantees your class makes, such as "MyString always has an even number of characters".
If implemented as intended, your object is always in a valid state - so a memberwise copy can very well copy the members directly without fear of breaking any guarantee. There is no advantage of passing already validated state through another round of state validation.
As AraK said, the best would be using an initializer list.
Not so simple (1):
Another reason to use getters/setters is not relying on implementation details. That's a strange idea for a copy CTor, when changing such implementation details you almost always need to adjust CDA anyway.
Not so simple (2):
To prove me wrong, you can construct invariants that are dependent on the instance itself, or another external factor. One (very contrieved) example: "if the number of instances is even, the string length is even, otherwise it's odd." In that case, the copy CTor would have to throw, or adjust the string. In such a case it might help to use setters/getters - but that's not the general cas. You shouldn't derive general rules from oddities.
我更喜欢使用外部类的接口来访问数据,以防您想要更改检索数据的方式。但是,当您在类的范围内并且想要复制复制值的内部状态时,我会直接使用数据成员。
更不用说如果 getter 不是内联的,您可能会节省一些函数调用。
I prefer using an interface for outer classes to access the data, in case you want to change the way it's retrieved. However, when you're within the scope of the class and want to replicate the internal state of the copied value, I'd go with data members directly.
Not to mention that you'll probably save a few function calls if the getter are not inlined.
如果你的 getter 是(内联的并且)不是虚拟的,那么在直接成员访问中使用它们没有任何优点或缺点——在我看来,它在风格方面看起来很愚蠢,但是,无论哪种方式都没有什么大不了的。
如果您的 getter 是虚拟的,那么就会有开销...但是,这正是您确实想要调用它们的时候,以防万一它们在子类中被重写!-)
If your getters are (inline and) not
virtual
, there's no pluses nor minuses in using them wrt direct member access -- it just looks goofy to me in terms of style, but, no big deal either way.If your getters are virtual, then there is overhead... but nevertheless that's exactly when you DO want to call them, just in case they're overridden in a subclass!-)
问问自己成本和收益是什么。
成本:更高的运行时开销。在 ctor 中调用虚拟函数是一个坏主意,但 setter 和 getter 不太可能是虚拟的。
好处:如果 setter/getter 做了一些复杂的事情,你就不会重复代码;如果它做了一些不直观的事情,你不会忘记这样做。
不同类别的成本/效益比会有所不同。一旦确定了该比率,就可以做出判断。当然,对于不可变类,您不需要 setter,也不需要 getter(因为 const 成员和引用可以是公共的,因为没有人可以更改/重新设置它们)。
Ask yourself what the costs and benefits are.
Cost: higher runtime overhead. Calling virtual functions in ctors is a bad idea, but setters and getters are unlikely to be virtual.
Benefits: if the setter/getter does something complicated, you're not repeating code; if it does something unintuitive, you're not forgetting to do that.
The cost/benefit ratio will differ for different classes. Once you're ascertained that ratio, use your judgment. For immutable classes, of course, you don't have setters, and you don't need getters (as const members and references can be public as no one can change/reseat them).
有一个简单的测试适用于许多设计问题,其中包括:添加副作用并查看哪些问题。
假设 setter 不仅分配一个值,还写入审核记录、记录消息或引发事件。您希望在复制对象时每个属性都发生这种情况吗?可能不是 - 所以在构造函数中调用 setter 在逻辑上是错误的(即使 setter 实际上只是赋值)。
There is a simple test that works for many design questions, this one included: add side-effects and see what breaks.
Suppose setter not only assigns a value, but also writes audit record, logs a message or raises an event. Do you want this happen for every property when copying object? Probably not - so calling setters in constructor is logically wrong (even if setters are in fact just assignments).
尽管我同意其他发帖者的观点,即您的示例中存在许多入门级 C++“禁忌”,但将其放在一边并直接回答您的问题:
在实践中,我倾向于创建许多但不是全部的成员字段*从 public 开始,然后在需要时将它们移至 get/set。
现在,我首先要说的是,这不一定是推荐的做法,许多从业者会厌恶这种做法,并说每个领域都应该有 setter/getter。
或许。但我发现在实践中这并不总是必要的。诚然,当我将一个字段从 public 更改为 getter 时,它会在以后引起痛苦,有时当我知道一个类将有什么用途时,我会对其进行设置/获取,并从一开始就将该字段设置为受保护或私有。
YMMV
RF
Although I agree with other posters that there are many entry-level C++ "no-no's" in your sample, putting that to the side and answering your question directly:
In practice, I tend to make many but not all of my member fields* public to start with, and then move them to get/set when needed.
Now, I will be the first to say that this is not necessarily a recommended practice, and many practitioners will abhor this and say that every field should have setters/getters.
Maybe. But I find that in practice this isn't always necessary. Granted, it causes pain later when I change a field from public to a getter, and sometimes when I know what usage a class will have, I give it set/get and make the field protected or private from the start.
YMMV
RF
编辑:回答编辑后的问题:)
如果您有一个简单的变量集合,不需要任何类型的验证,也不需要额外的处理,那么您可以考虑使用 POD。来自 Stroustrup 的常见问题解答:
简而言之,这不是JAVA。你不应该编写普通的 getter/setter,因为它们与暴露变量本身一样糟糕。
如果您复制另一个对象的变量,那么源对象应该处于有效状态。格式不正确的源对象最初是如何构造的?构造函数不应该做验证工作吗?修改成员函数不是负责通过验证输入来维护类不变吗?为什么要在复制构造函数中验证“有效”对象?
这是关于最优雅的风格,尽管在 C++ 中最优雅的代码通常具有最好的性能特征。
您应该使用
初始化列表
。在您的代码中,m_str1
是默认构造的,然后分配了一个新值。您的代码可能是这样的:@cbrulak IMO 您不应该在
复制构造函数
中验证cont.m_str1
。我所做的就是验证构造函数
中的内容。复制构造函数中的验证意味着您首先复制的是格式不正确的对象,例如:EDIT: Answering the edited question :)
If you have a simple collection of variables, that don't need any kind of validation, nor additional processing then you might consider using a POD instead. From Stroustrup's FAQ:
In short, this is not JAVA. you shouldn't write plain getters/setters because they are as bad as exposing the variables them selves.
If you are copying another object's variables, then the source object should be in a valid state. How did the ill formed source object got constructed in the first place?! Shouldn't constructors do the job of validation? aren't the modifying member functions responsible of maintaining the class invariant by validating input? Why would you validate a "valid" object in a copy constructor?
This is about the most elegant style, though in C++ the most elegant code has the best performance characteristics usually.
You should use an
initializer list
. In your code,m_str1
is default constructed then assigned a new value. Your code could be something like this:@cbrulak You shouldn't IMO validate
cont.m_str1
in thecopy constructor
. What I do, is to validate things inconstructors
. Validation incopy constructor
means you you are copying an ill formed object in the first place, for example:您应该使用初始值设定项列表,然后问题就变得毫无意义,如:
Matthew Wilson 的 Imperfect C++ 解释了有关成员初始值设定项列表的所有内容,以及如何将它们与 const 和/或引用结合使用以使代码更安全。
编辑:显示验证和 const 的示例:
You should use an initializer list, and then the question becomes meaningless, as in:
There's a great section in Matthew Wilson's Imperfect C++ that explains all about Member Initializer Lists, and about how you can use them in combination with const and/or references to make your code safer.
Edit: an example showing validation and const:
正如现在所写的(没有输入或输出的限定),您的 getter 和 setter(访问器和修改器,如果您愿意)绝对没有完成任何任务,因此您不妨将字符串公开并完成它。
如果真正的代码确实限定了字符串,那么您正在处理的很可能根本不是一个真正的字符串——相反,它只是看起来很像字符串的东西。在这种情况下,你真正做的是滥用类型系统,有点暴露字符串,而真正的类型只是有点像字符串。然后,您提供设置器来尝试强制执行实际类型与实际字符串相比的任何限制。
当您从这个方向看时,答案变得相当明显:您应该做的是为字符串定义一个实际的类,而不是使用 setter 使字符串表现得像其他(更受限制的)类型。键入您真正想要的类型。正确定义该类后,您可以将其实例公开。如果(似乎是这里的情况)为它分配一个以字符串开头的值是合理的,那么该类应该包含一个以字符串作为参数的赋值运算符。如果(这里似乎也是这种情况)在某些情况下将该类型转换为字符串是合理的,那么它还可以包含生成字符串作为结果的强制转换运算符。
与在周围类中使用 setter 和 getter 相比,这确实有所改进。首先也是最重要的,当您将它们放入周围的类中时,该类内的代码很容易绕过 getter/setter,从而失去对 setter 应该执行的任何内容的执行。其次,它保持了正常的符号。使用 getter 和 setter 会迫使您编写丑陋且难以阅读的代码。
C++ 中字符串类的主要优点之一是使用运算符重载,因此您可以替换如下内容:
strcpy(strcat(文件名,“.ext”));
和:
文件名 += ".ext";
以提高可读性。但是看看如果该字符串是强制我们执行 getter 和 setter 的类的一部分,会发生什么:
some_object.setfilename(some_object.getfilename()+".ext");
如果说有什么不同的话,那就是 C 代码实际上比这些混乱的代码更具可读性。另一方面,考虑一下如果我们使用定义运算符字符串和运算符 = 的类的公共对象正确完成工作会发生什么:
some_object.filename += ".ext";
很好,简单易读,就像它应该的那样。更好的是,如果我们需要对字符串强制执行某些操作,我们可以只检查那个小类,我们实际上只需要查看一两个特定的、众所周知的位置(operator=,可能是该类的一两个构造函数)即可知道它总是被强制执行的——这与我们使用 setter 来尝试完成这项工作时完全不同的情况。
As it's written right now (with no qualification of the input or output) your getter and setter (accessor and mutator, if you prefer) are accomplishing absolutely nothing, so you might as well just make the string public and be done with it.
If the real code really does qualify the string, then chances are pretty good that what you're dealing with isn't properly a string at all -- instead, it's just something that looks a lot like a string. What you're really doing in this case is abusing the type system, sort of exposing a string, when the real type is only something a bit like a string. You're then providing the setter to try to enforce whatever restrictions the real type has compared to a real string.
When you look at it from that direction, the answer becomes fairly obvious: rather than a string, with a setter to make the string act like some other (more restricted) type, what you should be doing instead is defining an actual class for the type you really want. Having defined that class correctly, you make an instance of it public. If (as seems to be the case here) it's reasonable to assign it a value that starts out as a string, then that class should contain an assignment operator that takes a string as an argument. If (as also seems to be the case here) it's reasonable to convert that type to a string under some circumstances, it can also include cast operator that produces a string as the result.
This gives a real improvement over using a setter and getter in a surrounding class. First and foremost, when you put those in a surrounding class, it's easy for code inside that class to bypass the getter/setter, losing enforcement of whatever the setter was supposed to enforce. Second, it maintains a normal-looking notation. Using a getter and a setter forces you to write code that's just plain ugly and hard to read.
One of the major strengths of a string class in C++ is using operator overloading so you can replace something like:
strcpy(strcat(filename, ".ext"));
with:
filename += ".ext";
to improve readability. But look what happens if that string is part of a class that forces us to go through a getter and setter:
some_object.setfilename(some_object.getfilename()+".ext");
If anything, the C code is actually more readable than this mess. On the other hand, consider what happens if we do the job right, with a public object of a class that defines an operator string and operator=:
some_object.filename += ".ext";
Nice, simple and readable, just like it should be. Better still, if we need to enforce something about the string, we can inspect only that small class, we really only have to look one or two specific, well-known places (operator=, possibly a ctor or two for that class) to know that it's always enforced -- a totally different story from when we're using a setter to try to do the job.
您是否预计字符串将如何返回,例如。修剪空白、检查空值等?与 SetMyString() 相同,如果答案是肯定的,那么您最好使用访问方法,因为您不必在无数地方更改代码,而只需修改那些 getter 和 setter 方法。
Do you anticipate how the string is returned, eg. white space trimmed, null checked, etc.? Same with SetMyString(), if the answer is yes, you are better off with access methods since you don't have to change your code in zillion places but just modify those getter and setter methods.
对于如何编写复制构造函数,没有什么灵丹妙药。
如果您的类仅具有提供创建的复制构造函数的成员
使用初始值设定项列表不共享状态(或至少看起来不这样做)的实例是一个好方法。
否则你必须真正思考。
请注意,您可以使用
smart_ptr
来解决潜在的段错误 - 但您可以在调试由此产生的错误时获得很多乐趣。当然,它还可以变得更有趣。
new beta(a.beta)
就是错误。...螺丝否则 - 请在编写复制构造函数时始终思考。
There's no silver bullet as how to write the copy constructor.
If your class only has members which provide a copy constructor that creates
instances which do not share state (or at least do not appear to do so) using an initializer list is a good way.
Otherwise you'll have to actually think.
Note that you can get around the potential segfault by using
smart_ptr
- but you can have a lot of fun debugging the resulting bugs.Of course it can get even funnier.
new beta(a.beta)
is wrong in case you somehow introduce polymorphism.... a screw the otherwise - please always think when writing a copy constructor.