3

我有一个带有复制构造函数的简单容器类。

您建议使用 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 与变量

  • 初始化列表或私有验证器函数并不是这个问题的一部分。我想知道这两种设计是否会使代码更易于维护和扩展。

  • 在这个例子中,一些人专注于字符串,但这只是一个例子,想象它是一个不同的对象。

  • 我不关心性能。我们不是在 PDP-11 上编程

4

11 回答 11

11

编辑:回答编辑的问题:)

这是一个简单的人为示例,用于 讨论使用 getter/setter 与 变量

如果您有一个简单的变量集合,不需要任何类型的验证,也不需要额外的处理,那么您可以考虑使用 POD。来自Stroustrup 的常见问题解答

一个设计良好的类向它的用户展示了一个干净简单的界面, 隐藏了它的表示并让它的用户不必知道那个表示。如果不应该隐藏表示 - 例如,因为用户应该能够以他们喜欢的方式更改任何数据成员 - 您可以将该类视为“只是一个普通的旧数据结构”

总之,这不是JAVA。您不应该编写简单的 getter/setter,因为它们与公开变量一样糟糕。

初始化列表或私有验证器函数并不是这个问题的一部分。我想知道这两种设计是否会使代码更易于维护和扩展。

如果您正在复制另一个对象的变量,则源对象应该处于有效状态。不正确的源对象最初是如何构造的?!构造函数不应该做验证工作吗?修改成员函数不是负责通过验证输入来维护类不变吗?为什么要在复制构造函数中验证“有效”对象?

我不关心性能。我们不是在 PDP-11 上编程

这是最优雅的风格,尽管在 C++ 中最优雅的代码通常具有最好的性能特征。


你应该使用一个initializer list. 在您的代码中,m_str1默认构造然后分配一个新值。您的代码可能是这样的:

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 您不应该在 IMOcont.m_str1中验证copy constructor. 我所做的是验证constructors. 验证copy constructor意味着您首先要复制格式错误的对象,例如:

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}
于 2009-09-29T02:09:25.563 回答
8

您应该使用初始化列表,然后问题变得毫无意义,如:

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

Matthew Wilson 的Imperfect C++中有一个很棒的部分解释了有关成员初始化器列表的所有内容,以及如何将它们与 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;
};
于 2009-09-29T02:12:04.053 回答
2

正如它现在所写的那样(没有输入或输出的限定),您的 getter 和 setter(如果您愿意,可以访问访问器和 mutator)完全没有完成任何事情,所以您不妨将字符串公开并完成它。

如果真正的代码确实限定了字符串,那么您正在处理的内容很可能根本不是一个正确的字符串——相反,它只是看起来很像字符串的东西。在这种情况下,您真正​​在做的是滥用类型系统,有点像公开字符串,而真正的类型只是有点像字符串。然后,您将提供 setter 以尝试强制执行实际类型与实际字符串相比的任何限制。

当你从那个方向看它时,答案变得相当明显:不是一个字符串,而是一个设置器来使字符串像其他(更受限制的)类型一样,你应该做的是为输入你真正想要的。正确定义该类后,您可以公开它的一个实例。如果(这里似乎就是这种情况)为它分配一个以字符串开头的值是合理的,那么该类应该包含一个将字符串作为参数的赋值运算符。如果(这里似乎也是这种情况)在某些情况下将该类型转换为字符串是合理的,它还可以包含生成字符串作为结果的强制转换运算符。

与在周围类中使用 setter 和 getter 相比,这提供了真正的改进。首先,当你把它们放在一个周围的类中时,该类中的代码很容易绕过 getter/setter,失去对 setter 应该强制执行的任何执行。其次,它保持了一个看起来很正常的符号。使用 getter 和 setter 会迫使您编写丑陋且难以阅读的代码。

C++ 中字符串类的主要优势之一是使用运算符重载,因此您可以替换以下内容:

strcpy(strcat(filename, ".ext"));

和:

filename += ".ext";

以提高可读性。但是看看如果该字符串是强制我们通过 getter 和 setter 的类的一部分会发生什么:

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

如果有的话,C 代码实际上比这个烂摊子更具可读性。另一方面,考虑如果我们使用定义操作符字符串和操作符=的类的公共对象正确地完成工作会发生什么:

some_object.filename += ".ext";

不错,简单易读,就像它应该的那样。更好的是,如果我们需要对字符串执行某些操作,我们可以只检查那个小类,我们实际上只需要查看一两个特定的、知名的地方(operator=,可能是那个类的一两个 ctor)知道它总是被强制执行的——这与我们使用 setter 尝试完成工作时完全不同。

于 2009-09-29T02:45:18.033 回答
1

问问自己成本和收益是什么。

成本:更高的运行时开销。在 ctor 中调用虚函数是个坏主意,但 setter 和 getter 不太可能是虚函数。

好处:如果 setter/getter 做了一些复杂的事情,你就不会重复代码;如果它做了一些不直观的事情,你不会忘记这样做。

不同等级的成本/收益比会有所不同。一旦确定了该比率,请使用您的判断。当然,对于不可变的类,您没有 setter,也不需要 getter(因为 const 成员和引用可以是公共的,因为没有人可以更改/重新安装它们)。

于 2009-09-29T02:12:13.770 回答
1

您是否预期如何返回字符串,例如。修剪空白,检查空值等?与 SetMyString() 相同,如果答案是肯定的,则最好使用访问方法,因为您不必在无数地方更改代码,而只需修改那些 getter 和 setter 方法。

于 2009-09-29T02:15:48.483 回答
1

如何编写复制构造函数没有灵丹妙药。如果您的类只有提供复制构造函数的成员,该构造函数创建不共享状态(或至少看起来不这样做)的实例,使用初始化列表是一个好方法。

否则你将不得不真正思考。

结构阿尔法{
   贝塔* m_beta;
   阿尔法():m_beta(新贝塔()){}
   ~alpha() { 删除 m_beta; }
   阿尔法(常量阿尔法&一个){
     // 需要复制吗?或者你有一个共享的状态?写时复制?
     m_beta = 新 beta(*a.m_beta);
     // 错误的
     m_beta = a.m_beta;
   }

请注意,您可以通过使用来绕过潜在的smart_ptr段错误 - 但调试由此产生的错误会很有趣。

当然,它可以变得更有趣。

  • 按需创建的成员。
  • new beta(a.beta)如果您以某种方式引入多态性,这是错误的。

...否则- 请在编写复制构造函数时始终思考。

于 2009-09-29T09:37:12.077 回答
1

为什么你需要 getter 和 setter?

简单:) - 它们保留不变量 - 即保证您的类生成,例如“MyString 总是有偶数个字符”。

如果按预期实现,您的对象始终处于有效状态 - 因此成员副本可以很好地直接复制成员,而不必担心破坏任何保证。通过另一轮状态验证传递已经验证的状态没有任何优势。

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.

于 2009-09-29T16:21:04.130 回答
0

我更喜欢使用外部类的接口来访问数据,以防您想更改检索数据的方式。但是,当您在类的范围内并且想要复制复制值的内部状态时,我会直接使用数据成员。

更不用说如果 getter 没有内联,您可能会保存一些函数调用。

于 2009-09-29T02:07:32.000 回答
0

如果您的吸气剂是(内联和)非virtual,那么在直接成员访问中使用它们没有优点也没有缺点——就风格而言,它对我来说看起来很傻,但是,无论哪种方式都没什么大不了的。

如果你的吸气剂是虚拟的,那么就会有开销......但那正是你想要调用它们的时候,以防它们在子类中被覆盖!-)

于 2009-09-29T02:07:39.170 回答
0

有一个简单的测试适用于许多设计问题,其中包括:添加副作用并查看哪些问题。

假设 setter 不仅分配一个值,而且还写入审计记录、记录消息或引发事件。复制对象时,您希望每个属性都发生这种情况吗?可能不会——所以在构造函数中调用 setter 在逻辑上是错误的(即使 setter 实际上只是赋值)。

于 2009-09-29T15:26:01.870 回答
-2

尽管我同意其他发帖者的观点,即您的示例中有许多入门级 C++“禁忌”,但将其放在一边并直接回答您的问题:

在实践中,我倾向于将我的许多但不是所有成员字段*一开始就公开,然后在需要时将它们移动到获取/设置。

现在,我将首先说这不一定是推荐的做法,许多从业者会厌恶这一点,并说每个领域都应该有 setter/getter。

也许。但我发现在实践中这并不总是必要的。当然,当我将字段从公共更改为 getter 时,它会导致痛苦,有时当我知道一个类将有什么用途时,我会对其进行设置/获取,并从一开始就将该字段设为受保护或私有。

YMMV

射频

  • 您将字段称为“变量”-我鼓励您仅将该术语用于函数/方法中的局部变量
于 2009-09-29T16:01:44.137 回答