C++ 可选函数参数:使用默认参数 (NULL) 还是重载函数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/703453/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Optional function parameters: Use default arguments (NULL) or overload the function?
提问by Frank
I have a function that processes a given vector, but may also create such a vector itself if it is not given.
我有一个处理给定向量的函数,但如果没有给出,也可以自己创建这样的向量。
I see two design choices for such a case, where a function parameter is optional:
对于这种情况,我看到了两种设计选择,其中函数参数是可选的:
Make it a pointer and make it NULL
by default:
使其成为指针并使其NULL
默认:
void foo(int i, std::vector<int>* optional = NULL) {
if(optional == NULL){
optional = new std::vector<int>();
// fill vector with data
}
// process vector
}
Or have two functions with an overloaded name, one of which leaves out the argument:
或者有两个具有重载名称的函数,其中一个省略了参数:
void foo(int i) {
std::vector<int> vec;
// fill vec with data
foo(i, vec);
}
void foo(int i, const std::vector<int>& optional) {
// process vector
}
Are there reasons to prefer one solution over the other?
是否有理由更喜欢一种解决方案?
I slightly prefer the second one because I can make the vector a const
reference, since it is, when provided, only read, not written. Also, the interface looks cleaner (isn't NULL
just a hack?). And the performance difference resulting from the indirect function call is probably optimized away.
我稍微喜欢第二个,因为我可以将向量作为const
参考,因为它在提供时只能读取,不能写入。此外,界面看起来更干净(NULL
不仅仅是一个黑客?)。间接函数调用导致的性能差异可能会被优化掉。
Yet, I often see the first solution in code. Are there compelling reasons to prefer it, apart from programmer laziness?
然而,我经常在代码中看到第一个解决方案。除了程序员的懒惰之外,是否有令人信服的理由更喜欢它?
采纳答案by LeopardSkinPillBoxHat
I would definitely favour the 2nd approach of overloaded methods.
我绝对喜欢重载方法的第二种方法。
The first approach (optional parameters) blurs the definition of the method as it no longer has a single well-defined purpose. This in turn increases the complexityof the code, making it more difficult for someone not familiar with it to understand it.
第一种方法(可选参数)模糊了方法的定义,因为它不再有一个明确定义的目的。这反过来又增加了代码的复杂性,使不熟悉它的人更难以理解它。
With the second approach (overloaded methods), each method has a clear purpose. Each method is well-structuredand cohesive. Some additional notes:
使用第二种方法(重载方法),每个方法都有明确的目的。每种方法都是结构良好且有凝聚力的。一些补充说明:
- If there's code which needs to be duplicated into both methods, this can be extracted out into a separate methodand each overloaded method could call this external method.
- I would go a step further and name each method differentlyto indicate the differences between the methods. This will make the code more self-documenting.
- 如果需要将代码复制到两个方法中,则可以将其提取到单独的方法中,并且每个重载方法都可以调用此外部方法。
- 我会更进一步,以不同的方式命名每种方法,以表明方法之间的差异。这将使代码更加自文档化。
回答by John Dibling
I would not use either approach.
我不会使用任何一种方法。
In this context, the purpose of foo() seems to be to process a vector. That is, foo()'s job is to process the vector.
在这种情况下, foo() 的目的似乎是处理一个向量。也就是说,foo() 的工作是处理向量。
But in the second version of foo(), it is implicitly given a second job: to create the vector. The semantics between foo() version 1 and foo() version 2 are not the same.
但是在 foo() 的第二个版本中,它被隐含地赋予了第二个工作:创建向量。foo() 版本 1 和 foo() 版本 2 之间的语义不一样。
Instead of doing this, I would consider having just one foo() function to process a vector, and another function which creates the vector, if you need such a thing.
如果您需要这样的东西,我会考虑只使用一个 foo() 函数来处理向量,以及另一个创建向量的函数,而不是这样做。
For example:
例如:
void foo(int i, const std::vector<int>& optional) {
// process vector
}
std::vector<int>* makeVector() {
return new std::vector<int>;
}
Obviously these functions are trivial, and if all makeVector() needs to do to get it's job done is literally just call new, then there may be no point in having the makeVector() function. But I'm sure that in your actual situation these functions do much more than what is being shown here, and my code above illustrates a fundamental approach to semantic design: give one function one job to do.
显然,这些函数是微不足道的,如果 makeVector() 完成工作所需要做的只是调用 new,那么使用 makeVector() 函数可能没有意义。但我敢肯定,在您的实际情况中,这些函数的作用远不止此处显示的内容,我上面的代码说明了语义设计的一种基本方法:给一个函数做一项工作。
The design I have above for the foo() function also illustrates another fundamental approach that I personally use in my code when it comes to designing interfaces -- which includes function signatures, classes, etc. That is this: I believe that a good interface is 1) easy and intuitive to use correctly, and 2) difficult or impossible to use incorrectly. In the case of the foo() function we are implictly saying that, with my design, the vector is required to already exist and be 'ready'. By designing foo() to take a reference instead of a pointer, it is both intuitive that the caller must already have a vector, and they are going to have a hard time passing in something that isn't a ready-to-go vector.
我上面对 foo() 函数的设计也说明了我个人在设计接口时在代码中使用的另一种基本方法——包括函数签名、类等。那就是:我相信一个好的接口是 1) 容易和直观地正确使用,以及 2) 难以或不可能错误地使用。在 foo() 函数的情况下,我们含蓄地说,根据我的设计,向量需要已经存在并“准备好”。通过将 foo() 设计为获取引用而不是指针,调用者必须已经有一个向量是很直观的,而且他们将很难传递一些不是准备好的向量的东西.
回答by David Ruhmann
While I do understand the complaints of many people regarding default parameters and overloads, there seems to be a lack of understanding to the benefits that these features provide.
虽然我确实理解许多人对默认参数和重载的抱怨,但似乎对这些功能提供的好处缺乏了解。
Default Parameter Values:
First I want to point out that upon initial design of a project, there should be little to no use for defaults if well designed. However, where defaults' greatest assets comes into play is with existing projects and well established APIs. I work on projects that consist of millions of existing lines of code and do not have the luxury to re-code them all. So when you wish to add a new feature which requires an extra parameter; a default is needed for the new parameter. Otherwise you will break everyone that uses your project. Which would be fine with me personally, but I doubt your company or users of your product/API would appreciate having to re-code their projects on every update. Simply, Defaults are great for backwards compatibility!This is usually the reason you will see defaults in big APIs or existing projects.
默认参数值:
首先,我想指出,在项目的初始设计时,如果设计良好,默认值应该几乎没有用处。然而,默认值的最大资产发挥作用的是现有项目和完善的 API。我从事由数百万行现有代码组成的项目,并且没有奢侈地重新编码它们。因此,当您希望添加需要额外参数的新功能时;新参数需要一个默认值。否则,您将破坏使用您项目的每个人。这对我个人来说没问题,但我怀疑您的公司或您的产品/API 的用户是否愿意在每次更新时重新编码他们的项目。 简单地说,默认值非常适合向后兼容!这通常是您会在大型 API 或现有项目中看到默认值的原因。
Function Overrides:The benefit of function overrides is that they allow for the sharing of a functionality concept, but with with different options/parameters. However, many times I see function overrides lazily used to provide starkly different functionality, with just slightly different parameters. In this case they should each have separately named functions, pertaining to their specific functionality (As with the OP's example).
函数覆盖:函数覆盖的好处是它们允许共享功能概念,但具有不同的选项/参数。但是,很多时候我看到函数覆盖懒惰地用于提供截然不同的功能,只是参数略有不同。在这种情况下,它们每个都应该具有单独命名的函数,与它们的特定功能有关(与 OP 的示例一样)。
These, features of c/c++ are good and work well when used properly. Which can be said of most any programming feature. It is when they are abused/misused that they cause problems.
这些,c/c++ 的特性很好,如果使用得当,效果很好。这可以说是大多数编程功能。当它们被滥用/误用时,它们就会引起问题。
Disclaimer:
I know that this question is a few years old, but since these answers came up in my search results today (2012), I felt this needed further addressing for future readers.
免责声明:
我知道这个问题已经有几年了,但由于这些答案出现在我今天(2012 年)的搜索结果中,我觉得这需要为未来的读者进一步解决。
回答by Sid Sarasvati
A references can't be NULL in C++, a really good solution would be to use Nullable template. This would let you do things is ref.isNull()
C++ 中的引用不能为 NULL,一个非常好的解决方案是使用 Nullable 模板。这会让你做的事情是 ref.isNull()
Here you can use this:
在这里你可以使用这个:
template<class T>
class 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;
};
Now you can have
现在你可以拥有
void foo(int i, Nullable<AnyClass> &optional = Nullable<AnyClass>()) {
//you can do
if(optional.isNull()) {
}
}
回答by Sid Sarasvati
I agree, I would use two functions. Basically, you have two different use cases, so it makes sense to have two different implementations.
我同意,我会使用两个功能。基本上,您有两个不同的用例,因此有两个不同的实现是有意义的。
I find that the more C++ code I write, the fewer parameter defaults I have - I wouldn't really shed any tears if the feature was deprecated, though I would have to re-write a shed load of old code!
我发现我编写的 C++ 代码越多,我拥有的参数默认值就越少 - 如果该功能被弃用,我真的不会流泪,尽管我将不得不重新编写大量旧代码!
回答by Diego Sevilla
I usually avoid the first case. Note that those two functions are different in what they do. One of them fills a vector with some data. The other doesn't (just accept the data from the caller). I tend to name differently functions that actually do different things. In fact, even as you write them, they are two functions:
我通常避免第一种情况。请注意,这两个函数的作用不同。其中之一用一些数据填充向量。另一个没有(只接受来自调用者的数据)。我倾向于给实际做不同事情的不同函数命名。实际上,即使在您编写它们时,它们也是两个功能:
foo_default
(or justfoo
)foo_with_values
foo_default
(或只是foo
)foo_with_values
At least I find this distinction cleaner in the long therm, and for the occasional library/functions user.
至少我发现这种区别在长期以及偶尔的库/函数用户中更清晰。
回答by Mehrdad Afshari
I, too, prefer the second one. While there are not much difference between the two, you are basically usingthe functionality of the primary method in the foo(int i)
overload and the primary overload would work perfectly without caring about existence of lack of the other one, so there is more separation of concerns in the overload version.
我也更喜欢第二种。虽然两者之间没有太大区别,但您基本上是在重载中使用主要方法的功能,foo(int i)
并且主要重载可以完美运行而无需关心是否存在其他方法,因此在重载中有更多的关注点分离重载版。
回答by JaredPar
In C++ you should avoid allowing valid NULL parameters whenever possible. The reason is that it substantially reduces callsite documentation. I know this sounds extreme but I work with APIs that take upwards of 10-20 parameters, half of which can validly be NULL. The resulting code is almost unreadable
在 C++ 中,您应该尽可能避免允许有效的 NULL 参数。原因是它大大减少了调用站点文档。我知道这听起来很极端,但我使用的 API 需要 10-20 个以上的参数,其中一半可以有效地为 NULL。结果代码几乎不可读
SomeFunction(NULL, pName, NULL, pDestination);
If you were to switch it to force const references the code is simply forced to be more readable.
如果您将其切换为强制 const 引用,则代码只会被强制提高可读性。
SomeFunction(
Location::Hidden(),
pName,
SomeOtherValue::Empty(),
pDestination);
回答by Richard Corden
I'm squarely in the "overload" camp. Others have added specifics about your actual code example but I wanted to add what I feel are the benefits of using overloads versus defaults for the general case.
我完全属于“超载”阵营。其他人已经添加了有关您的实际代码示例的细节,但我想添加我认为使用重载与在一般情况下使用默认值的好处。
- Any parameter can be "defaulted"
- No gotcha if an overriding function uses a different value for its default.
- It's not necessary to add "hacky" constructors to existing types in order to allow them to have default.
- Output parameters can be defaulted without needing to use pointers or hacky global objects.
- 任何参数都可以“默认”
- 如果覆盖函数对其默认值使用不同的值,则没有问题。
- 没有必要向现有类型添加“hacky”构造函数以允许它们具有默认值。
- 输出参数可以默认设置,无需使用指针或 hacky 全局对象。
To put some code examples on each:
在每个上放一些代码示例:
Any parameter can be defaulted:
任何参数都可以默认:
class A {}; class B {}; class C {};
void foo (A const &, B const &, C const &);
inline void foo (A const & a, C const & c)
{
foo (a, B (), c); // 'B' defaulted
}
No danger of overriding functions having different values for the default:
没有覆盖具有不同默认值的函数的危险:
class A {
public:
virtual void foo (int i = 0);
};
class B : public A {
public:
virtual void foo (int i = 100);
};
void bar (A & a)
{
a.foo (); // Always uses '0', no matter of dynamic type of 'a'
}
It's not necessary to add "hacky" constructors to existing types in order to allow them to be defaulted:
没有必要向现有类型添加“hacky”构造函数以允许它们被默认:
struct POD {
int i;
int j;
};
void foo (POD p); // Adding default (other than {0, 0})
// would require constructor to be added
inline void foo ()
{
POD p = { 1, 2 };
foo (p);
}
Output parameters can be defaulted without needing to use pointers or hacky global objects:
无需使用指针或 hacky 全局对象即可默认输出参数:
void foo (int i, int & j); // Default requires global "dummy"
// or 'j' should be pointer.
inline void foo (int i)
{
int j;
foo (i, j);
}
The only exception to the rule re overloading versus defaults is for constructors where it's currently not possible for a constructor to forward to another. (I believe C++ 0x will solve that though).
与默认值相比,重新重载规则的唯一例外是构造函数,其中当前无法将构造函数转发给另一个构造函数。(我相信 C++ 0x 会解决这个问题)。
回答by j_random_hacker
Generally I agree with others' suggestion to use a two-function approach. However, if the vector created when the 1-parameter form is used is always the same, you could simplify things by instead making it static and using a default const&
parameter instead:
一般来说,我同意其他人使用双功能方法的建议。但是,如果使用 1 参数形式时创建的向量始终相同,则可以通过将其设为静态并使用默认const&
参数来简化事情:
// Either at global scope, or (better) inside a class
static vector<int> default_vector = populate_default_vector();
void foo(int i, std::vector<int> const& optional = default_vector) {
...
}