【问题标题】:Is it a good practise to put non member function inside a class?将非成员函数放在类中是一种好习惯吗?
【发布时间】:2014-10-23 08:17:06
【问题描述】:

我正在阅读这样的代码:

class Member
{
public:
    friend std::istream& operator>>(std::istream& in, Member& m)
    {
        in >> m.name >> m.bYear >> m.bMonth;
        return in;
    }

    friend std::ostream& operator<<(std::ostream& out,const Member& m)
    {
        out << m.name << " " << m.bYear << "." << m.bMonth;
        return out;
    }

private:
    std::string name;
    int year;
    int month;
};

我以前从未见过这种方式。使用friend 在类体内定义非成员函数是一种好习惯吗?有什么优点和缺点吗?

【问题讨论】:

  • 这是我自己经常做的事情(对于琐碎的内联 I/O),因为它表达了函数与类之间的密切联系。我没有看到任何真正的负面因素这样做。无论如何,您都必须在类定义中声明函数。
  • 如果类和它的友元都是模板,那么在类外定义友元会变得相当麻烦。 stackoverflow.com/questions/16814037#16814372
  • @Theolodis:不是重复的,friend的使用不在讨论中。
  • @JamesKanze:也许我们对“凌乱”的定义不同。必须在类之前声明函数模板,并且(在大多数情况下)还要在函数之前声明类模板,这对我来说确实很麻烦。
  • @JamesKanze:我的(有点切题的)评论是,如果它们是模板,那么由于需要额外的声明,它会变得混乱,正如我链接到的问题所证明的那样。如果它们不是模板,那么您是对的,将接口与实现分开不会再混乱(根据个人喜好,也许更可取)。抱歉,如果我对稍微超出问题范围的内容发表评论而造成混淆。

标签: c++


【解决方案1】:

在类体中与朋友一起定义非成员函数是一种好习惯吗?

我会说,无所谓的做法。

有什么优点和缺点吗?

优点

  • 运算符可以引用类范围内的类成员(嵌套类、typedef、枚举、常量、静态函数等),而无需显式地为它们加上类名前缀

  • 隐式使用流式函数很方便inline - 没有一个定义规则的麻烦

  • 友谊意味着您可以方便地访问所有非公共成员

  • 学习类源代码的人更有可能注意到流功能

  • 正如 Mike Seymour 评论的那样,如果该类是一个模板,那么定义一个朋友可以让您省略运算符的 template &lt;...&gt; 方面,并将实例参数简单地称为 @987654325 @ 而不是 const Member&lt;...&gt;&amp;

缺点

  • 您可能想要一个外联函数定义,以便稍后修改实现,只需要重新链接(而不是重新编译)客户端代码

  • 您授予的友谊在功能上可能不是必需的,这会减少封装并因此减少可维护性

  • 寻找非成员流式操作符的人可能不会考虑查看类代码

  • 您可能会争辩说它“弄乱”了类定义源代码,从而更难吸收所有实际类成员

像往常一样,干净分离接口和实现的好处——无论是管理物理依赖关系(需要重新编译,而不仅仅是重新链接)和人类可读性——对于不同的高级库使用的低级库和应用程序,并且对于本地实现的“私有”支持要低得多(例如,.cpp 文件中匿名命名空间中的类,仅由该单个翻译单元使用,或者 - 更是如此 - private 嵌套类) .

【讨论】:

【解决方案2】:

一般来说,这不是好习惯;理想情况下, 实现甚至不会与类在同一个文件中 定义。 (理想情况下,我们也不必声明 头文件中的私有部分。)有很多 但是,有正当理由的例外:

  • 最明显的是在非常简单的辅助类中,其中 真的不足以证明分开这两个部分是合理的。 如果助手类是在本地定义的,则尤其如此, 在源文件中,而不是在标题中。

  • 另一种情况是针对朋友的,尤其是在模板中。如果 我写(甚至在模板中)friend void f( MyClass&amp; ),然后 我已将非模板声明为朋友,我必须 为每个实现一个单独的非模板函数 实例化类型。如果我提供内联实现 in 类定义,然而,编译器会自动 为每种类型创建单独的非模板函数 用过的。这是定义的一个非常常见的动机 operator&gt;&gt;operator&lt;&lt; 在课堂上,因为它们不能 会员;通常他们会被声明为friend,即使他们 不需要访问私人成员,只是这样他们就可以 这样定义的。

最后,如果没有其他函数声明或 运算符,它们仅在类中可见,或者在 ADL 中可见。 这不应该是一个问题,只要功能有 至少一个涉及类的参数。

【讨论】:

  • 如果你追求理想,希望有合适的模块(这意味着根本不需要头文件)。委员会正在努力……
  • @Deduplicator 当然可以。但在那之前......(我强烈认为我们需要export 用于模板,因为我们没有模块的概念。我也有点反对命名空间,因为它只做了一半的工作,并且我们需要模块。)
【解决方案3】:

可能的专业人士: 如果您有所有或大多数其他函数来处理在类体内定义的类的私有成员,则更易于阅读和维护。它将事物保持在一起。

缺点: 类主体中定义的函数出现在每个编译单元中,而不是仅在一个编译相应的 .cpp 文件时出现,您可以将它们放入其中。

【讨论】:

  • 如果函数因为性能而应该是内联的,或者它是模板化的,这并不是一个真正的问题。
  • 实际上,在大型项目中,维护起来要困难得多。第一条规则是客户端代码所需的接口定义与实现代码保持在一个单独的文件中,以便客户端看不到实现的修改。
  • @JamesKanze:在这个例子中,耦合足够强,分离出定义根本不会买任何东西,只会做更多的工作。当然,减少耦合可能会有所不同。
  • @Deduplicator: “分离定义根本不买任何东西,只会做更多的工作”,它忽略了常见的重新编译与“只是”重新链接内联的后果与离线(和在实现文件中)定义。 Lakos 称之为“物理”依赖/“重新编译防​​火墙”和所有爵士乐....
  • @Deduplicator 我不关注你。实施就是实施,对客户可见的越少越好。 (在大型项目中,您通常无法在没有架构审查的情况下更改接口,因为这会破坏代码。确保这一点的最简单方法是将接口和实现放在两个单独的文件中,并且不检查除非您被授权更改它,否则使用接口输出文件。)
猜你喜欢
  • 2017-07-15
  • 2022-01-20
  • 2018-09-26
  • 2016-06-01
  • 2019-11-20
  • 1970-01-01
  • 1970-01-01
  • 2020-04-24
  • 2017-10-11
相关资源
最近更新 更多