简体   繁体   English

C ++:复制构造函数:直接使用getter或访问成员变量?

[英]C++: Copy constructor: Use getters or access member vars directly?

I have a simple container class with a copy constructor. 我有一个带有复制构造函数的简单容器类。

Do you recommend using getters and setters, or accessing the member variables directly? 您是否建议使用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;
}
  • In the example, all code is inline, but in our real code there is no inline code. 在示例中,所有代码都是内联的,但在我们的实际代码中没有内联代码。

Update (29 Sept 09): 更新(09年9月29日):

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 这是一个简单的人为例子,讨论使用getter / setter和变量

  • 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. 一些ppl在这个例子中专注于字符串,但它只是一个例子,想象它是一个不同的对象。

  • I'm not concerned about performance. 我不关心表现。 we're not programming on the PDP-11 我们不是在PDP-11上编程

EDIT: Answering the edited question :) 编辑:回答编辑的问题:)

this is simple contrived example to discuss using getters/setters vs variables 这是一个简单的人为例子, 讨论使用getter / setter和 变量

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. 如果您有一个简单的变量集合,不需要任何类型的验证,也不需要额外的处理,那么您可以考虑使用POD。 From Stroustrup's FAQ : 来自Stroustrup的FAQ

A well-designed class presents a clean and simple interface to its users, hiding its representation and saving its users from having to know about that representation. 精心设计的类为其用户提供了一个干净简单的界面, 隐藏其表示并使用户不必了解该表示。 If the representation shouldn't be hidden - say, because users should be able to change any data member any way they like - you can think of that class as "just a plain old data structure" 如果不应该隐藏表示 - 比如说,因为用户应该能够以他们喜欢的方式更改任何数据成员 - 您可以将该类视为“只是一个普通的旧数据结构”

In short, this is not JAVA. 简而言之,这不是JAVA。 you shouldn't write plain getters/setters because they are as bad as exposing the variables them selves. 你不应该写普通的getter / setter,因为它们和它们自己暴露变量一样糟糕。

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. 我想知道这两种设计是否会使代码更容易维护和扩展。

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? 为什么要在复制构造函数中验证“有效”对象?

I'm not concerned about performance. 我不关心表现。 we're not programming on the PDP-11 我们不是在PDP-11上编程

This is about the most elegant style, though in C++ the most elegant code has the best performance characteristics usually. 这是最优雅的风格,但在C ++中,最优雅的代码通常具有最佳的性能特征。


You should use an initializer list . 您应该使用initializer list In your code, m_str1 is default constructed then assigned a new value. 在您的代码中, m_str1是默认构造的, 然后分配一个新值。 Your code could be something like this: 你的代码可能是这样的:

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbrulak You shouldn't IMO validate cont.m_str1 in the copy constructor . @cbrulak您不应该IMO验证copy constructor cont.m_str1 What I do, is to validate things in constructors . 我所做的是验证constructors东西。 Validation in copy constructor means you you are copying an ill formed object in the first place, for example: copy constructor验证意味着您首先复制一个格式错误的对象,例如:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}

You should use an initializer list, and then the question becomes meaningless, as in: 您应该使用初始化列表,然后问题变得毫无意义,如:

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

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. Matthew Wilson的Imperfect C ++中有一个很棒的部分解释了所有关于成员初始化列表的内容,以及如何将它们与const和/或引用结合使用以使代码更安全。

Edit : an example showing validation and const: 编辑 :显示验证和const的示例:

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};

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. 正如它现在所写的那样(没有输入或输出的限定条件)你的getter和setter(如果你愿意的话,访问器和mutator)完全没有任何成就,所以你也可以将字符串公之于众,并完成它。

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. 然后,您将提供setter以尝试强制实际类型与真实字符串相比的任何限制。

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. 当你从那个方向看它时,答案变得相当明显:不是一个字符串,用一个setter来使字符串像其他(更受限制的)类型一样,你应该做的是为它定义一个实际的类。打字你真的想要。 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. 与在周围的类中使用setter和getter相比,这提供了真正的改进。 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. 首先,当你将它们放在一个周围的类中时,该类中的代码很容易绕过getter / setter,从而失去了setter应该执行的任何强制执行。 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. 使用getter和setter会强制您编写简单难看且难以阅读的代码。

One of the major strengths of a string class in C++ is using operator overloading so you can replace something like: C ++中字符串类的主要优点之一是使用运算符重载,因此您可以替换以下内容:

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: 但是看看如果该字符串是强制我们通过getter和setter的类的一部分会发生什么:

some_object.setfilename(some_object.getfilename()+".ext");

If anything, the C code is actually more readable than this mess. 如果有的话,C代码实际上比这个混乱更具可读性。 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=: 另一方面,考虑如果我们正确地完成工作会发生什么,使用定义运算符字符串和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. 更好的是,如果我们需要对字符串执行某些操作,我们只能检查那个小类,我们实际上只需要查看一个或两个特定的,众所周知的地方(operator =,可能是该类的ctor或两个)知道它总是被强制执行 - 这是一个完全不同的故事,当时我们正在使用二传手尝试做这项工作。

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. 在ctors中调用虚函数是一个坏主意,但是setter和getter不太可能是虚拟的。

Benefits: if the setter/getter does something complicated, you're not repeating code; 好处:如果setter / getter做了一些复杂的事情,你就不会重复代码; 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,并且您不需要getter(因为const成员和引用可以是公共的,因为没有人可以更改/重置它们)。

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. 与SetMyString()相同,如果答案是肯定的,那么您最好使用访问方法,因为您不必更改您的代码,只需修改那些getter和setter方法。

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. 否则你将不得不考虑。

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

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. 请注意,您可以使用smart_ptr绕过潜在的段smart_ptr - 但是您可以通过调试生成的错误来获得很多乐趣。

Of course it can get even funnier. 当然它可以变得更有趣。

  • Members which are created on demand. 按需创建的成员。
  • new beta(a.beta) is wrong in case you somehow introduce polymorphism. 如果你以某种方式介绍多态性, new beta(a.beta)错误的

... a screw the otherwise - please always think when writing a copy constructor. ......一个螺丝,否则 -请写一个拷贝构造函数时,总会想起。

Why do you need getters and setters at all? 为什么你需要吸气剂和二传手?

Simple :) - They preserve invariants - ie guarantees your class makes, such as "MyString always has an even number of characters". 简单:) - 它们保留不变量 - 即保证你的类产生,例如“MyString总是有偶数个字符”。

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. 正如AraK所说,最好的是使用初始化列表。


Not so simple (1): 不那么简单(1):
Another reason to use getters/setters is not relying on implementation details. 使用getter / setter的另一个原因不是依赖于实现细节。 That's a strange idea for a copy CTor, when changing such implementation details you almost always need to adjust CDA anyway. 这对于复制CTor来说是一个奇怪的想法,当改变这样的实现细节时,你几乎总是需要调整CDA。


Not so simple (2): 不那么简单(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. 在这种情况下,复制CTor必须抛出或调整字符串。 In such a case it might help to use setters/getters - but that's not the general cas. 在这种情况下,它可能有助于使用setter / getters - 但这不是一般的cas。 You shouldn't derive general rules from oddities. 你不应该从奇怪的事物中得出一般规则。

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,你可能会保存一些函数调用。

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. 如果你的获取者(内联和)不是virtual ,那么使用它们就没有优势也没有直接成员访问权限 - 它在风格方面对我来说只是看起来很傻,但是,无论如何都没有什么大不了的。

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!-) 如果你的干将都是虚拟的,再有就是开销......但无论如何这也正是当你想打电话给他们,万一他们覆盖在子类- !)

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. 假设setter不仅分配值,还会写入审计记录,记录消息或引发事件。 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). 可能不是 - 所以在构造函数中调用setter在逻辑上是错误的(即使setter实际上只是赋值)。

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: 虽然我同意其他海报,你的样本中有许多入门级C ++“禁忌”,但是把它放在一边并直接回答你的问题:

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. 在实践中,我倾向于将许多但不是所有的成员字段*公共开始,然后在需要时将它们移动到get / set。

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. 当然,当我将一个字段从公共字段更改为一个getter时,它会导致疼痛,有时当我知道一个类将具有什么用法时,我会给它set / get并使字段从一开始就受到保护或私有。

YMMV 因人而异

RF RF

  • you call fields "variables" - I encourage you to use that term only for local variables within a function/method 你调用字段“变量” - 我鼓励你只将这个术语用于函数/方法中的局部变量

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM