繁体   English   中英

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

[英]C++: Copy constructor: Use getters or access member vars 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;
}
  • 在示例中,所有代码都是内联的,但在我们的实际代码中没有内联代码。

更新(09年9月29日):

其中一些答案写得很好但是他们似乎忽略了这个问题的重点:

  • 这是一个简单的人为例子,讨论使用getter / setter和变量

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

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

  • 我不关心表现。 我们不是在PDP-11上编程

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

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

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

精心设计的类为其用户提供了一个干净简单的界面, 隐藏其表示并使用户不必了解该表示。 如果不应该隐藏表示 - 比如说,因为用户应该能够以他们喜欢的方式更改任何数据成员 - 您可以将该类视为“只是一个普通的旧数据结构”

简而言之,这不是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您不应该IMO验证copy constructor cont.m_str1 我所做的是验证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!
    }
}

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

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

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

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

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

当你从那个方向看它时,答案变得相当明显:不是一个字符串,用一个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代码实际上比这个混乱更具可读性。 另一方面,考虑如果我们正确地完成工作会发生什么,使用定义运算符字符串和operator =的类的公共对象:

some_object.filename += ".ext";

很好,简单,可读,就像应该的那样。 更好的是,如果我们需要对字符串执行某些操作,我们只能检查那个小类,我们实际上只需要查看一个或两个特定的,众所周知的地方(operator =,可能是该类的ctor或两个)知道它总是被强制执行 - 这是一个完全不同的故事,当时我们正在使用二传手尝试做这项工作。

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

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

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

不同类别的成本/效益比会有所不同。 一旦你确定了这个比例,就用你的判断。 对于不可变类,当然,您没有setter,并且您不需要getter(因为const成员和引用可以是公共的,因为没有人可以更改/重置它们)。

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

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

否则你将不得不考虑。

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;
   }

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

当然它可以变得更有趣。

  • 按需创建的成员。
  • 如果你以某种方式介绍多态性, new beta(a.beta)错误的

......一个螺丝,否则 -请写一个拷贝构造函数时,总会想起。

为什么你需要吸气剂和二传手?

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

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

正如AraK所说,最好的是使用初始化列表。


不那么简单(1):
使用getter / setter的另一个原因不是依赖于实现细节。 这对于复制CTor来说是一个奇怪的想法,当改变这样的实现细节时,你几乎总是需要调整CDA。


不那么简单(2):
为了证明我的错误,您可以构造依赖于实例本身或其他外部因素的不变量。 一个(非常有条理的)例子:“如果实例的数量是偶数,则字符串长度是偶数,否则它是奇数。” 在这种情况下,复制CTor必须抛出或调整字符串。 在这种情况下,它可能有助于使用setter / getters - 但这不是一般的cas。 你不应该从奇怪的事物中得出一般规则。

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

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

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

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

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

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

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

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

现在,我将第一个说这不一定是推荐的做法,许多从业者会厌恶这一点,并说每个领域都应该有定位器/吸气剂。

也许。 但我发现在实践中并不总是必要的。 当然,当我将一个字段从公共字段更改为一个getter时,它会导致疼痛,有时当我知道一个类将具有什么用法时,我会给它set / get并使字段从一开始就受到保护或私有。

因人而异

RF

  • 你调用字段“变量” - 我鼓励你只将这个术语用于函数/方法中的局部变量

暂无
暂无

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

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