Optional function parameters: Use default arguments (NULL) or overload the function?

72,501

Solution 1

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 complexity of 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-structured and cohesive. Some additional notes:

  • If there's code which needs to be duplicated into both methods, this can be extracted out into a separate method and each overloaded method could call this external method.
  • I would go a step further and name each method differently to indicate the differences between the methods. This will make the code more self-documenting.

Solution 2

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.

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.

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.

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.

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.

Solution 3

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.

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).

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.

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.

Solution 4

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()

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()) {

   }
}

Solution 5

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!

Share:
72,501

Related videos on Youtube

Frank
Author by

Frank

Updated on November 29, 2020

Comments

  • Frank
    Frank over 3 years

    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:

    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.

    Yet, I often see the first solution in code. Are there compelling reasons to prefer it, apart from programmer laziness?

  • Diego Sevilla
    Diego Sevilla about 15 years
    Totally agree with the second paragraph... Default parameters (apart from in template parameters) are almost never a good design choice.
  • John Dibling
    John Dibling about 15 years
    I would counter that if the function really does two things, then that is a design flaw; a Broken Window (see "Pragmatic Programmer") that should be repaired.
  • dkretz
    dkretz about 15 years
    This is the best advice. First, simplify. I was surprised to find it at the bottom of the answers stack.
  • Richard Corden
    Richard Corden almost 15 years
    Except that they're really the only option for constructors (in C++'03 anywa).
  • Graeme
    Graeme almost 13 years
    Fully agree; best answer in my opinion
  • bobobobo
    bobobobo about 11 years
    I'm kind of ok with this answer but I disagree with your reasoning. The point of overloads is each method already does the same thing, that's why we don't care to distinguish them by name. Usually overloads will vary only in type of parameters they accept -- a set of print( int ), print( double ) type functions comes to mind. Where there is an optional return parameter, that should just mean the caller may or may not care to rx that optional result. I do agree with the last sentence in this answer: if the functions do different things, then yes, name each method differently.
  • bobobobo
    bobobobo about 11 years
    You're right, naming the default value (and perhaps #defining it as 0) is a lot easier to read than func( arg, 0, 0, 0, 0, 0, 0 );
  • Jeremy
    Jeremy almost 9 years
    But the creation of the vector in version 2 is not externally visible - so apart from accepting additional arguments the semantics of version 1 and version 2 are the same, aren't they?
  • Sebastian Wagner
    Sebastian Wagner almost 9 years
    Also have a look at std::optional.