【问题标题】:Can a "future-safe" compare operator be crafted in C++?可以用 C++ 制作“未来安全”的比较运算符吗?
【发布时间】:2011-05-29 13:17:32
【问题描述】:

大家好,

是否可以在 C++ 中制作未来安全的比较运算符 (==)?

我面临的问题是我们有一个包含多个成员的类。我们有一个比较运算符来验证对象的 instance-1 是否与 instance-2 具有相同的值。

即我们可以做

class blarg {
    .....
};

.....

blarg   b1(..initializers...);
blarg   b2 = b1;

if (b1 == b2) {
    ... then do something ....
}

但是,我有一位同事向班级添加了新成员,但未能更新比较运算符。这导致了我们需要一段时间才能解决的问题。

是否有编码实践,我的意思是其他而不是代码审查(对我们来说失败了),或者编码方法、设计、模板、魔豆,任何可以帮助避免这种情况?

我的第一反应是使用memcmp 命令。但是,在阅读了“Comparing structures in C vs C++”的堆栈溢出条目后,我发现这可能会出现问题,因为 C++ 类不仅包含内部成员数据。

其他人如何处理这个问题?

提前感谢您的帮助。

【问题讨论】:

  • 是的。停止雇用容易犯错误的程序员。
  • 或者停止使用 C++ 来支持一些具有自省的语言。或者使用代码生成器来生成这些东西。
  • @Noah:不幸的是,可靠的程序员严重短缺。
  • @Fred - 我不知道吗。成为该地区唯一完美的程序员真是太难了。实际上,我必须故意犯错,以免人们发现并开始嫉妒。

标签: c++ class operator-overloading


【解决方案1】:

嗯,显而易见的解决方案是在扩展原始类时更加小心。 :) “更加小心”包括代码审查之类的东西,但显然这不是万无一失的。

因此,从哲学角度而非技术角度解决这个问题通常可以提供洞察力。在这种情况下,哲学是成为一个偏执的程序员。假设您今天编写的代码将在几个月或几年后被一些愚蠢的人破坏。 (根据@Noah 的 cmets 进行编辑:通常情况下,傻瓜就是我自己。作为一个偏执的程序员可能比其他人更能保护我免受我自己的伤害。)如果你能做一些事情来确保当nitwit 确实会破坏您的代码,但在产品发货之前出现故障,这会有所帮助。

我喜欢使用的两件事是静态断言和单元测试。可以在您的 operator== 代码中使用静态断言来验证您的类的 sizeof 是否符合您的预期。例如:

bool MyClass::operator==(const MyClass& rhs) const
{
  static_assert(sizeof(MyClass) == sizeof(foo_) + sizeof(bar_))
  ...
}

...其中foo_bar_ 是成员变量。当类的大小发生变化时,这将中断编译。

由于静态断言通常采用模板类的形式,当表达式为 false 时将无法编译。这写起来可能有点棘手(但这是一个有趣的练习——考虑一下如果你尝试将char test_[0] 成员添加到类中会发生什么)。幸运的是,这个轮子已经被发明出来了。参见Boost 的示例,我认为新的 MSVC 编译器也附带了一个。

【讨论】:

  • 这个方法背后的想法很好,但我认为它经常会由于成员对齐的原因而失败。
  • 这个词一个词:填充。
  • 我的经验也是如此。我为保护下一个修改我的代码而不加注意的白痴所做的所有努力最终都让我比其他人更频繁地成为那个白痴。
  • @wilhelmtell:max_align 技巧如何帮助补偿类的成员的对齐?
  • 我认为硬编码已知大小会有效,而不是使用sizeof(MyClass)。至少在你尝试另一个编译器或不同的编译器开关之前......
【解决方案2】:

好的,现在给出真正的答案。

对您的对象进行适当的单元测试完全应该可以解决这种错误。这正是他们擅长指出的事情。

【讨论】:

  • 只要将成员变量添加到类的程序员也在单元测试中为相同的更改添加了适当的检查。他没有更新operator==,那么他记得更新他的单元测试的可能性有多大?
  • 即使这样也无济于事。如果添加新成员的开发人员忘记了比较运算符,他也可能忘记编写单元测试。
  • 对那个的两个响应。首先,正确使用单元测试要求您首先编写测试。开发人员不应该忘记他们是否遵循这种方法。其次,如果有问题的测试有一个 == 测试,如果它有这样的运算符也应该有,那么更新失败仍然会导致编译失败并提醒开发人员他的错误。但是不,尽管它大大降低了搞砸的几率,但仍然不完美。
  • @Daniel - 我在 C++ 中遇到的 TDD 的唯一问题是糟糕的设计选择……这几乎是由于一开始就没有进行 TDD 造成的。
  • “对您的对象进行适当的单元测试” - 就此而言,operator== 的适当更新会打开它,所以我建议:-)。问题似乎是有人更改了班级,但没有想过根本这种更改会如何影响比较。 可能他们对测试所做的任何更改都会发现问题。如果他们没有更改类接口,则现有测试可能会捕获它。我认为即使有良好的现有测试也不能保证。当然,他们的错误意味着他们不会审查明确关注 operator== 的测试。
【解决方案3】:

解决这个问题的唯一真正方法是根本不需要该类的赋值运算符。你问那是什么?如果我有动态内存等需要深拷贝怎么办?

如果有人经常向您的类添加数据成员,这意味着该类不是该数据的简单容器。通过将需要自定义赋值运算符的任何内容包装到一个单独的简单类中,您可以将自定义赋值代码放入该简单类中,将需要自定义赋值的复杂类中的数据替换为该新简单类的实例,并避免需要为复杂类编写自定义赋值运算符。

简单的类可能不会经常更改(如果有的话),因此需要进行的赋值运算符维护工作会更少。奇怪的是,经历这个过程最终也会为您留下一个更清洁、更可行的架构。

【讨论】:

    【解决方案4】:

    在某些情况下,可以通过使所有成员具有某种形式的属性或类似的属性来实现,这些属性可以在运行时进行迭代和单独比较。

    对于普通课程,这并不是一个真正的选择 - 我建议对此进行单元测试,就像 Noah Roberts 在他的回答中指出的那样。

    【讨论】:

      【解决方案5】:

      听起来可能很傻,但你可以写一个清单,列出(可能)需要考虑一个类的所有成员的所有事情。我能想到:

      • 用户定义的构造函数(特别是它们的初始化列表)
      • 析构函数(如果您的类曾经使用需要它的成员,但如果析构函数需要考虑多个成员,您可能会遇到麻烦)
      • swap
      • operator<<(std::ostream&, const blarg&),或其他形式的(反)序列化
      • operator<
      • operator==
      • [C++0x]hash函数
      • 可能还有什么我忘记了。

      当你添加一个成员,或者改变一个成员的类型或含义(或者可能删除一个成员,但在这种情况下,任何使用它的东西通常会自行爆炸,任何不使用它的东西都可能会失败'没有注意到它已经消失了)。

      如果所有这些函数都定义(或至少声明)在一起会更容易:当然,无论如何我都会将前三个“生命周期”的东西放在一起,而且我从来没有在没有审查过的情况下接触成员。理想情况下,这三个“比较”也很接近,因为它们需要相互一致,所以一旦习惯根深蒂固,清单最终可能只是对班级的简单一瞥。 “我已经改变了类结构,所以我需要看看这里,看看除了我已经想到的改变之外还有哪些功能需要复习......”

      显然,如果你能管理它,你就会遵循开放/封闭原则,而这永远不会发生。就我个人而言,我从来没有做到过。

      【讨论】:

        猜你喜欢
        • 2023-03-30
        • 1970-01-01
        • 1970-01-01
        • 2011-10-16
        • 1970-01-01
        • 2011-10-21
        • 1970-01-01
        • 2021-04-06
        • 2023-03-03
        相关资源
        最近更新 更多