重载一个函数以接受指针或引用是不好的编程习惯吗?

19
例如,如果我有一个结构体 FooSpec
struct FooSpec { /* lots of data */ };

提供以下两个函数的重载形式是不好的实践吗?还是我应该只选择一个并坚持使用它?

Foo *createFoo(const FooSpec *spec) { /* ... */ }
Foo *createFoo(const FooSpec &spec) { return createFoo(&spec); }

同时拥有这两者会导致意想不到的问题吗?


7
您不应该有任何技术问题,但是如果看到这样的 API ,我肯定会感到困惑。 - Matteo Italia
3
如果一个函数只是调用另一个函数,那么不一致的接口会带来什么好处? - Drew Dormann
6
如果你想保留传递 nullptr 的选项,则需要使用指针版本,而引用版本是为了方便某些客户作者避免键入和号 & 而添加的复杂性。如果 NULL 不是一个选项,则引用版本是正确的方法,指针版本同样增加了复杂性,这次是为了避免键入星号 *。在任一情况下都没有实质性的好处,两者都是以增加复杂度为代价进行的。仅因为你可以并不意味着你应该。选择适当的模型,并告诉客户端“这是您的接口”。 - WhozCraig
2
好的,如果你正在将编码规范迁移到使用引用而不是 const *(或相反,但这对我来说没有太多意义)的过程中,这并不是一个坏习惯。 - didierc
1
@WhozCraig:你应该把那个评论变成一个答案。 - Matt
显示剩余7条评论
3个回答

21

针对特定事情应该只有一种方法来完成。这将使代码更清晰易懂。因此,你应该根据语义只选择一种方式:

  • 如果 nullptr 是传递给 createFoo 的有意义的值,则声明函数以接受指针。

  • 如果不应该将 nullptr 传递给 createFoo,则声明参数为引用,以便编译器拒绝那些尝试传递 nullptr 的人。

现在你的代码用户会说:“啊,我可以将 null 传递给该函数,太棒了!”或者“好的,这个函数需要一个实际对象才能正常工作。

在整个项目中坚持这种约定,使代码接口更加连贯。

或者如 WhozCraig 所说,

选择适当的模型,并告诉客户“这是你的接口”。


有人可能会说,“如果对象必须存在,则通过引用传递”,这意味着函数不必担心无效指针值和测试它们。 - Thomas Matthews
此外,对于您的函数而言,nullptr 应该具有一定的含义。例如:如果您获得了 nullptr 并且 throw invalid_argument,这是毫无意义的。在这种情况下,强制使用引用会更好。 - edmz

5
我能想到一个较为有用的理由来做这件事。
通过使用Foo*版本,可以检查空值并处理该情况,在不使用引用的情况下。 Foo&则不需要检查空值。
这样,调用者可以说“我知道这不是空值,因为我已经检查过”,从而在函数内部节省了一个分支。
然而,一旦方法的成本超过了非常低的阈值,这种节省分支的好处就没有意义了。

3

软件代码库不是一个静态的对象,它会随着使用者的需求而发展。如果一个混乱的界面被认为是“不好的做法”(我宁愿说是一件傻事),那么一个代码库在经过重构和代码改进后可能会陷入这种情况。

在这种情况下,如果你正在将编码约定迁移到使用引用而不是const指针(或反之亦然,但对我来说没有太多意义),那么这并不是不良实践。不良实践可能是进行这些演变而不记录它,并且其他人不知道其中一个方法已被弃用,或者根本不知道该使用哪个方法。

还有一种可能性是在使用const指针时传递nullptr_t(或像我这样的老手使用NULL指针)。我不确定这样做的好处是什么,因为你可以根据参数重载方法,但在不断变化的API上下文中这是有意义的。


† 它还活着!!!


那么您会说,一个接口从一开始就不应该设计成这样吗? - Matt
@Matt 可能不应该这样做,但我相信总会有人想出一个好的理由。你知道一个好的理由吗? - didierc
1
正如@WhozCraig在他的评论中所说,我的原因只是为了避免多打一个&*。我想这并不是一个真正“好”的理由。 - Matt
2
不要为了简洁而牺牲清晰度(尽管我承认我自己也没有遵循得足够好)。 - didierc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接