Main Content

AUTOSAR C++14 Rule A8-4-3

Common ways of passing parameters should be used.

Since R2021b

Description

Rule Definition

Common ways of passing parameters should be used.

Rationale

If you follow well-established conventions for passing parameters, a developer or reviewer can determine from your function signature whether a parameter is an input parameter, an output parameter, or a different type of parameter.

These conventions are commonly used to pass parameters to a function f(X):

  • In: If the input parameter data type X is cheap to copy or cannot be copied (for instance, the std::unique_ptr type), pass the parameter by value as f(X). Otherwise, pass the parameter by const reference as f(const X &).

  • Out: If the output parameter data type X is expensive to move, pass the parameter by reference as f(X &). Otherwise, do not pass a parameter, but instead return the value as X f().

  • In/Out: Pass the parameter by lvalue reference as f(X &).

  • Consume: Pass the parameter by rvalue reference as f(X &&).

  • Forward: Pass the parameter by template rvalue reference as template<typename T> f(T &&).

Polyspace Implementation

The checker flags these incorrect ways of passing parameters:

  • In parameters:

    • You pass by value an input parameter that is expensive to copy:

      f(X); //X is expensive to copy

    • You pass by reference an input parameter that is cheap to copy:

      f(X &); //X is cheap to copy
      f(const X &); //X is cheap to copy

    The checker considers a data type that has a size less than twice sizeof(void *) as cheap to copy.

  • Out parameters:

    • You return by value an output parameter that is expensive to move:

      X f(); //X is expensive to move

    • You pass by reference an output parameter that is cheap to move:

      f (X &); //X is cheap to move

    The checker considers a fundamental data type that has a size less than eight times sizeof(void *) as cheap to move.

The checker does not include pass by pointers under the umbrella of pass by reference.

Resolving a violation of this rule can sometimes involve invasive changes. For instance, if the checker suggests that an output parameter can be returned by value, but the function already returns a value, you have to rewrite your code significantly. You have to combine the output parameter and already returned value into a structure or n-tuple and then return this structure or n-tuple. If the output parameter and already returned value are not semantically related, combining them into a structure might not be appropriate. In this case, add a comment to the result or code to avoid another review. See Address Results in Polyspace User Interface Through Bug Fixes or Justifications.

Troubleshooting

If you expect a rule violation but Polyspace® does not report it, see Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

#include <iostream>
#include <array>
#include <numeric>

typedef struct smallStruct {
    char x;
    char y;
}smallStruct;

void sum(smallStruct& aStruct) { //Noncompliant
    std::cout<<aStruct.x+aStruct.y;
}



typedef struct largeStruct {
    std::array<int,20> arrayOfIntegers;
    int init;
}largeStruct;

void add(largeStruct aStruct) { //Noncompliant
    std::cout<<std::accumulate(aStruct.arrayOfIntegers.cbegin(),
                     aStruct.arrayOfIntegers.cend(), aStruct.init);
}

In this example, functions sum and add only read the contents of their parameters. Therefore, these parameters are input parameters.

  • The function sum() with signature:

    void sum(smallStruct& aStruct);
    takes as argument an object of type smallStruct that is cheap to copy. The argument can be passed by value instead of reference.

  • The function add() with signature:

    void add(largeStruct aStruct);
    takes as argument an object of type largeStruct that is expensive to copy. The argument can be passed by reference instead of value.

#include <array>
#include <algorithm>

void init(int& val) { //Noncompliant
    val = 0;
}

typedef struct largeStruct {
    std::array<int,100> arrayOfIntegers;
    int init;
}largeStruct;

largeStruct reset(void) { //Noncompliant
    largeStruct aStruct;
    std::fill(aStruct.arrayOfIntegers.begin(), aStruct.arrayOfIntegers.end(), 0);
    aStruct.init = 0;
    return aStruct;
}

In this example:

  • The function init() with signature:

    void init(int& val);
    takes as an argument an object of type int that is cheap to move. The object can be returned by value instead of being passed by reference.

  • The function reset() with signature:

    largeStruct reset(void);
    returns an object of type largeStruct that is expensive to copy. The object can be returned by reference instead of being returned by value.

Check Information

Group: Declarators
Category: Advisory, Non-automated

Version History

Introduced in R2021b