Reputation: 13968
The objective is to help code readability for other programmers.
When creating a function prototype, I tend to put simple types (int, char) directly into the prototype, while more complex structures are generally passed as pointers (struct complex *).
Of course, this is "in general", and there are some obvious counter example : if a simple parameter must be modified by the function, pass a pointer to it instead.
But now, suppose that, among my function parameters, I get a complex but not-so-huge structure to pass, in read only mode (it won't be modified). Up to now, I would do something like this :
int myFunction1(int var1, const complexStruct* var2Ptr);
and of course it works. But now, I am wondering, since the content of the structure won't be modified anyway, would it be better to define such a function prototype instead :
int myFunction2(int var1, complexStruct var2);
It's difficult to tell. My guts tell me myFunction2 is probably easier to read, hence to use, notably by novice programmers, which tend to dislike pointers. Seasoned programmers will probably not mind.
I also guess that the amount of data copied to the stack as part of parameter list will be higher for myFunction2 (assuming myFunction2 is not automatically inlined by the compilator). Of course, as long as complexStruct is not too large, it should not be an issue. But it means the size of the struct is a decision factor.
Last, I guess that myFunction2 might be easier to inline by a compiler. But that one is just a wild guess.
Anyway, since both function prototypes look equivalent, I'm a bit clueless as to which one might be preferable, and wonder if there are other considerations, potentially more important, to factor in the decision.
Summary of comments received so far :
In a nutshell, prefer const struct* under most circumstances.
Upvotes: 2
Views: 6087
Reputation: 25449
Especially for functions that “belong” to a certain data type, I prefer passing a pointer as the first argument (like the this
pointer in C++). It also makes the signatures of mutating and non-mutating functions more consistent.
Consider a struct stack
with a stack_push
, stack_pop
(both mutating) and a (non-mutating) stack_top
function. The type might be as small as
struct stack
{
size_t capacity_;
size_t size_;
char * data_;
};
so we should not be worried about copying the few words around. (It might actually be even faster than dereferencing a non-local pointer.)
Still, I find this set of “member functions”
extern int stack_init(struct stack * this);
extern int stack_deinit(struct stack * this);
extern int stack_push(struct stack * this, char value);
extern int stack_pop(struct stack * this);
extern int stack_top(const struct stack * this);
extern int stack_size(const struct stack * this);
void
usage_example()
{
struct stack mystack;
if (stack_init(&mystack) < 0) { /* handle error */ }
if (stack_push(&mystack, 'a') < 0) { /* handle error */ }
assert(stack_top(&mystack) == 'a');
assert(stack_size(&mystack) == 1);
stack_deinit(&mystack);
}
much more consistent than this one:
extern int stack_init(struct stack * this);
extern int stack_deinit(struct stack * this);
extern int stack_push(struct stack * this, char value);
extern int stack_pop(struct stack * this);
/* Duckling asks: What is the difference between 'this' and 'self'? */
extern int stack_top(struct stack self);
extern int stack_size(struct stack self);
void
usage_example()
{
struct stack mystack;
if (stack_init(&mystack) < 0) { /* handle error */ }
if (stack_push(&mystack, 'a') < 0) { /* handle error */ }
/* Duckling asks: Are you sure you didn't forget the '&' here? */
assert(stack_top(mystack) == 'a');
assert(stack_size(mystack) == 1);
stack_deinit(&mystack);
}
If possible, I think the fact that something is const
should be the concern of the compiler and not force the programmer to use another calling convention.
It's irrelevant to the question but in case someone asks: The return value of the stack_top
and stack_pop
functions was meant to be the respective character or –1 on underflow. The other functions return 0 on success and a negative error code on error (eg out of memory).
Upvotes: 1