我有一个处理给定向量的函数,但如果没有给出,也可以自己创建这样的向量.
我看到这种情况有两种设计选择,其中一个函数参数是可选的:
将它设为指针并NULL
默认设置为:
void foo(int i, std::vector* optional = NULL) { if(optional == NULL){ optional = new std::vector (); // fill vector with data } // process vector }
或者有两个带有重载名称的函数,其中一个省略了参数:
void foo(int i) { std::vectorvec; // fill vec with data foo(i, vec); } void foo(int i, const std::vector & optional) { // process vector }
是否有理由选择一种解决方案而不是另一种?
我稍微偏爱第二个,因为我可以将向量作为const
引用,因为它在提供时只能读取而不能写入.此外,界面看起来更干净(NULL
不仅仅是一个黑客?).并且间接函数调用产生的性能差异可能会被优化掉.
然而,我经常在代码中看到第一个解决方案.除了程序员的懒惰之外,是否有令人信服的理由更喜欢它?
我不会使用任何一种方法.
在这种情况下,foo()的目的似乎是处理一个向量.也就是说,foo()的工作是处理向量.
但是在foo()的第二个版本中,隐含地给出了第二个工作:创建向量.foo()版本1和foo()版本2之间的语义不一样.
而不是这样做,我会考虑只有一个foo()函数来处理一个向量,而另一个函数创建向量,如果你需要这样的东西.
例如:
void foo(int i, const std::vector& optional) { // process vector } std::vector * makeVector() { return new std::vector ; }
显然这些函数是微不足道的,如果所有makeVector()需要做的就是完成它的工作就完全是调用new,那么使用makeVector()函数可能没有意义.但我确信在你的实际情况下,这些函数比这里显示的更多,而我上面的代码说明了语义设计的基本方法:给一个函数做一个工作.
我上面提到的foo()函数的设计也说明了我个人在我的代码中使用的另一种基本方法,当涉及到设计接口时 - 包括函数签名,类等等.这就是:我相信一个好的接口是1)容易和直观正确使用,2)难以或不可能错误地使用.在foo()函数的情况下,我们隐含地说,根据我的设计,向量必须已经存在并且已经"准备好".通过设计foo()来获取引用而不是指针,调用者必须已经有一个向量是直观的,并且他们将很难传递一些不是现成的向量.
我绝对赞成重载方法的第二种方法.
第一种方法(可选参数)模糊了方法的定义,因为它不再具有明确定义的目的.这反过来又增加了代码的复杂性,使不熟悉代码的人更难理解代码.
使用第二种方法(重载方法),每种方法都有明确的目的.每种方法都具有良好的结构和凝聚力.一些额外的说明:
如果需要将代码复制到两个方法中,则可以将其提取到单独的方法中,并且每个重载方法都可以调用此外部方法.
我会更进一步,并以不同的方式命名每个方法,以指示方法之间的差异.这将使代码更加自我记录.
虽然我确实理解许多人对默认参数和过载的抱怨,但似乎对这些功能提供的好处缺乏了解.
默认参数值:
首先,我想指出,在项目的初始设计中,如果设计得很好,默认情况应该几乎没有用.但是,默认情况下,最大的资产发挥作用的是现有项目和完善的API.我从事包含数百万现有代码行的项目,并且无需重新编写所有代码.因此,当您希望添加需要额外参数的新功能时; 新参数需要默认值.否则,您将破坏使用您项目的每个人.哪个对我个人没问题,但我怀疑你的公司或产品/ API的用户会喜欢在每次更新时重新编码他们的项目. 简单来说,默认值非常适合向后兼容! 这通常是您将在大API或现有项目中看到默认值的原因.
函数覆盖:函数覆盖 的好处是它们允许共享功能概念,但具有不同的选项/参数.但是,很多时候我看到函数覆盖延迟用于提供截然不同的功能,只是略有不同的参数.在这种情况下,它们应该各自具有与其特定功能相关的单独命名的功能(与OP的示例一样).
这些c/c ++的功能很好,并且在正确使用时效果很好.这可以说是大多数编程功能.当他们被滥用/滥用时,他们会导致问题.
免责声明:
我知道这个问题已经有几年了,但是由于今天(2012年)我的搜索结果中出现了这些答案,我觉得这需要进一步解决未来的读者问题.
C++中的引用不能为NULL,一个非常好的解决方案是使用Nullable模板.这会让你做的事情是ref.isNull()
在这里你可以使用这个:
templateclass Nullable { public: Nullable() { m_set = false; } explicit Nullable(T value) { m_value = value; m_set = true; } Nullable(const Nullable &src) { m_set = src.m_set; if(m_set) m_value = src.m_value; } Nullable & operator =(const Nullable &RHS) { m_set = RHS.m_set; if(m_set) m_value = RHS.m_value; return *this; } bool operator ==(const Nullable &RHS) const { if(!m_set && !RHS.m_set) return true; if(m_set != RHS.m_set) return false; return m_value == RHS.m_value; } bool operator !=(const Nullable &RHS) const { return !operator==(RHS); } bool GetSet() const { return m_set; } const T &GetValue() const { return m_value; } T GetValueDefault(const T &defaultValue) const { if(m_set) return m_value; return defaultValue; } void SetValue(const T &value) { m_value = value; m_set = true; } void Clear() { m_set = false; } private: T m_value; bool m_set; };
现在你可以拥有
void foo(int i, Nullable&optional = Nullable ()) { //you can do if(optional.isNull()) { } }
我同意,我会使用两个功能.基本上,您有两种不同的用例,因此有两种不同的实现是有意义的.
我发现我编写的C++代码越多,我的参数默认值就越少 - 如果该功能被弃用,我真的不会流泪,尽管我不得不重新编写一堆旧代码!