3

I'd like to work out conventions on passing parameters to functions/methods. I know it's a common issue and it has been answered many times, but I searched a lot and found nothing that fully satisfies me.

Passing by value is obvious and I won't mention this. What I came up with is:

  1. Passing by non-const reference means, that object is MODIFIED
  2. Passing by const reference means, that object is USED
  3. Passing by pointer means, that a reference to object is going to be STORED. Whether ownership is passed or not will depend on the context.

It seems to be consistent, but when I want to pick heap-allocated object and pass it to 2. case parameter, it'd look like this:

void use(const Object &object) { ... }

//...

Object *obj = getOrCreateObject();
use(*obj);

or

Object &obj = *getOrCreateObject();
use(obj);

Both look weird to me. What would you advise?

PS I know that one should avoid raw pointers and use smart instead (easier memory managment and expressiveness in ownership) and it can be the next step in refactoring the project I work on.

5
  • The problem is that (1) C++ has no common conventions, (2) existing conventions differ depending on language standard or project scope (3) C++ language has no means to express object lifetime constraints, immutability and many other things. So no convention is good enough. The only certain thing is that starting with C++11 it is recommended to use smart pointers instead of raw pointers to express ownership. So yeah, that is a good idea. Commented Mar 25, 2018 at 12:17
  • I know, discussion on conventions can even lead to a holy war. So which alternative I depicted is more common in a well written code? *obj = get() or &obj = *get()? Commented Mar 25, 2018 at 12:18
  • @PKua Well written code would probably use type erasure to return by value. Commented Mar 25, 2018 at 12:22
  • 2
    If you return a raw pointer then Object &obj = *getOrCreateObject(); won't be a good choice because function can legally return a null pointer and you'll end up with invalid reference. If the function is not supposed to return a null pointer value then there is no point in returning pointer instead of a reference. Commented Mar 25, 2018 at 12:28
  • @VVT good point. I haven't thought about returning a reference. So it would end up like Object &obj = getOrCreateObject(); if I want to work on that specific object, not a copy? Commented Mar 25, 2018 at 12:40

3 Answers 3

1

You can use these conventions if you like. But keep in mind that you cannot assume conventions when dealing with code written by other people. You also cannot assume that people reading your code are aware of your conventions. You should document an interface with comments when it might be ambiguous.

Passing by pointer means, that object is going to be STORED. Who's its owner will depend on the context.

I can think of only one context where the ownership of a pointer argument should transfer to the callee: Constructor of a smart pointer.

Besides possible intention of storing, a pointer argument can alternatively have the same meaning as a reference argument, with the addition that the argument is optional. You typically cannot represent an optional argument with a reference since they cannot be null - although with custom types you could use a reference to a sentinel value.

Both look weird to me. What would you advise?

Neither look weird to me, so my advise is to get accustomed.

Sign up to request clarification or add additional context in comments.

1 Comment

I know I can't assume conventions. I wan't to work out them, discuss with others and start to use consequently. I should have written "reference to object is going to be stored" - the only one (like for unique_ptr) or one of many (like for shared_ptr). I'll edit that.
1

The main problem with your conventions is that you make no allowance for the possibility of interfacing to code (e.g. written by someone else) that doesn't follow your conventions.

Generally speaking, I use a different set of conventions, and rarely find a need to work around them. (The main exception will be if there is a need to use a pointer to a pointer, but I rarely need to do that directly).

Passing by non-const reference is appropriate if ANY of the following MAY be true;

  • The object may be changed;
  • The object may be passed to another function by a non-const reference [relevant when using third party code by developers who choose to omit the const - which is actually something a lot of beginners or lazy developers do];
  • The object may be passed to another function by a non-const pointer [relevant when using third party code be developers who choose to omit the const, or when using legacy APIs];
  • Non-const member functions of the object are called (regardless of whether they change the object or not) [also often a consideration when using third-party code by developers who prefer to avoid using const].

Conversely, const references may be passed if ALL of the following are true;

  • No non-mutable members of the object are changed;
  • The object is only passed to other functions by const reference, by const pointer, or by value;
  • Only const member functions of the object are called (even if those members are able to change mutable members.

I'll pass by value instead of by const reference in cases where the function would copy the object anyway. (e.g. I won't pass by const reference, and then construct a copy of the passed object within the function).

Passing non-const pointers is relevant if it is appropriate to pass a non-const reference but there is also a possibility of passing no object (e.g. a nullptr).

Passing const pointers is relevant if it is appropriate to pass a const reference but there is also a possibility of passing no object (e.g. a nullptr).

I would not change the convention for either of the following

  • Storing a reference or pointer to the object within the function for later use - it is possible to convert a pointer to a reference or vice versa. And either one can be stored (a pointer can be assigned, a reference can be used to construct an object);
  • Distinguishing between dynamically allocated and other objects - since I mostly either avoid using dynamic memory allocation at all (e.g. use standard containers, and pass them around by reference or simply pass iterators from them around) or - if I must use a new expression directly - store the pointer in another object that becomes responsible for deallocation (e.g. a std::smart_pointer) and then pass the containing object around.

1 Comment

Actually your test for whether third party accepts by non-const is not at all necessary. The only question relevant is whether or not object is mutable. If it is not; use const as the less side effects a function allows; the less error prone it is. If your function uses functions that dont mutate the object but yet require a non const obect; all you have to do is const cast away the object. As long as the object is truly not mutated; then no UB. Furthermore if you declare a const function as non const just to suit third party functions you further add to the confusion.
0

In my opionion, they are the same. In the first part of your post, you are talking about the signature, but your example is about function call.

1 Comment

I show what possible function call structures are implied by such a signature.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.