views:

477

answers:

7

Is there a common way to express the usage of arguments in C++? I want to implicitly tell the consumers of my class how the arguments they pass will be used by the class.

Examples:

  1. I own your argument (will clean it up)
  2. I will hold a reference to your argument during my lifetime (so you should NOT delete it while I'm stile alive)
  3. I will use your argument only during construction and won't hold a reference

Is there a common way to express these things simply using the method declaration? I think in the first case a std::auto_ptr would make sense. In the second case I usually take a pointer to avoid someone passing a value from the stack which would invalidate my reference quickly, or alternatively a shared_ptr. In the third case I take a reference to allow values from the stack.

How do you deal with this? Also is it necessary to rely on smart pointers here, or can one express such things simply by using naked references and pointers somehow?

+1  A: 

If you're looking for clarity, documentation is one good way to deal with this. Of course, that assumes that people pay attention to your docs.

Aside from using existing classes with known usage patterns, you could create your own class. With a class that the compiler could auto-generate from a normal parameter (using operators you would write, of course), you could more accurately specify the contract without making it more difficult to pass the parameters. There are drawbacks to this (extra classes, more layers for the user to see and understand, naming responsibilities), but it is an option to consider.

If you're in an environment where IntelliSense is available, make sure that the necessary information is present in the parameter description. That could save you from any of these other steps.

John Fisher
+2  A: 

Maybe I'm missing the point of you question, but if your method prototypes in your header file are setup properly then that will do it.
"Implicitly" the user will know that your given method only accepts a reference to the parameter, or that a given parameter is read-only (const).. etc....

Steve Lazaridis
+3  A: 

I don't know if there is a common idiom, but I do know that the one sure-fire way that I provide this information is comments in the interface header. The users of the class won't always read them, won't always remember them, and they'll screw them up quicker than you can blink, but the information WILL be there.

Now, that being said, I've also taken to being against keeping references to something that some other piece of the system owns. It's not always practical (or sensible) to restructure so that your class owns everything (or doesn't keep references after a method call returns), but it's the safest way to do things, and either one is easier for the caller to understand.

The big problem with retaining references is that your callers will never remember that they aren't allowed to destroy these things, and you'll eventually end up with 'use of deleted object' type failures.

Michael Kohne
So you would go the safe way and clone() the arguments to be sure they won't be deleted?
driAn
@driAn I clone what I need, because the situation Micheal Kone mentions just gets worse when your multithreading!
Robert Gould
+2  A: 

While documentation is the only answer, it is unfortunately not a useful one. The research that I've been doing shows that many clients never read the documentation of methods that they are using.

I would recommend putting it into the nomenclature of your method names or the argument names, since at least that is visible in the autocomplete window. It's ugly, but it gets the message through :)

In my own tool (for Java/Eclipse) I have a tag for that that allows me to announce to the user when he needs to know something important about the parameter.

Uri
Interesting.. could you give me a tiny naming sample?
driAn
My tool places tags in the JavaDoc and highlights things in the call, so I address it differently.
Uri
However, in terms of method variables, you could think of a nomenclature (especially if you write a whole library). So that "surrenderedX" as an argument name means that the receiver will now own it, "loanedX" could mean that the receiver will keep a copy, etc.,
Uri
The point I'm trying to make is that if you write an entire library and establish a nomenclature that users can expect, then since they are often exposed to the variable names via autocomplete (depends on IDE) there is a higher chance they would notice it. But it is still a crapshoot.
Uri
In the end, without static analysis you can never assume that users do what your documentation requires. Documentation is just a recommendation, unfortunately.
Uri
Thanks! I'll think about that..
driAn
+6  A: 

Our team has similar coding conventions to the ones you suggest:

1 - auto_ptr argument means that the class will take control of memory management for the object. (We don't use this much.)

2 - shared_ptr means that the class will probably use the argument for an extended period of time, and in particular may store off its own shared_ptr to the object.

3 - Plain reference means that the argument will only be used for the duration of the call.

We treat this as a coding standard. It isn't something we document for each and every call.

David Norman
+2  A: 

I don't work with code that uses boost or STL, as I don't work with systems that support them particularly well, but I have found the following standards useful...

I use const references and value parameters interchangeably. (I use references if the values are larger than a register -- this is simply an optimisation.) The callee will take a copy by value if it needs it, so the argument can be destroyed straight away. (Case 3.)

I use const pointers to indicate that the input is by-reference, and that the callee will take a copy by reference. The lifespan of the argument must exceed that of the callee. (Case 2.)

I use non-const pointers to indicate that the input is by-reference, and that the callee will modify the pointee if necessary. Lifespan of the argument is deliberately undefined in the general case. (i.e., case 2 or 3.)

I don't use non-const references myself, purely because it's not obvious syntactically at the call point what is going on, but this is simply my personal inclination. I have my reasons, but that doesn't mean anybody else has to agree! So one might usefully use the reference/pointer distinction to indicate lifespan, as I have described in the const case. In that case, maybe pointer = case 2, and reference = case 3.

I indicate case 1 (callee takes ownership) by a comment; this is rare enough in my code that I don't worry about it too much.

brone
+2  A: 

I use the following method prefixes to denote object ownership. Ownership implies responsibility for deletion.

Y.take_ZZZ(x)

The caller is giving ownership of x to Y and caller must not further access x. (because Y could even immediately delete x).

Y.own_ZZZ(x)

Similar to take; the caller is giving ownership of x to Y but caller may continue to reference x and expect Y will not immediately delete x (at least as long a Y exists and it is assumed that Y will exist at least throughout the context of the caller knowing of Y). Usually Y will not delete x until Y is destroyed (although Y may transfer ownership).

Y.know_ZZZ(x)

The caller is providing a pointer/reference to x (the caller may or may not itself own x), but Y is not to take ownership of x. Y will expect that x will exist as long as it does.

x = Y.provide_ZZZ()

x is an object that Y owns. Y returns a reference (&) to the object so the caller can 'know' it. The object it may be initially a null pointer until it is required, Y may not create the object until it is needed. Whether x is dynamically allocated or not is hidden from the caller, Y does whatever is needed to return the reference to an instances of the object to be provided. x will then remain instantiated as long as Y exists. Sometimes I will provided a corresponding method Y.done_with_ZZZ(x) so that the caller can tell Y it is done can delete x.

x = Y.give_ZZZ()

Y relinquishes x to the caller and will no further reference it. The caller will then own x and be responsible for deleting it or giving it to another object.

x = Y.lend_ZZZ()

Y provides a reference/pointer to x to the caller (opposite of know_ZZZ) Y retains ownership. (similar to provide, but Y does not instantiate X, so it will either have x and return the reference(pointer) or it will not and return null.

x = Y.check_out_ZZZ() and Y.check_in_ZZZ(x)

With checkout, Y gives the caller exclusive access to x until the caller checks it in.

x = Y.create_ZZZ()

Y will create x and immediately relinquish it to the caller (factory function).

The ownership can be further strengthened by using smart pointers, but generally I use these naming conventions without smart pointers unless there is going to be a more complicated sequence of ownership transactions, the scope of ownership is not very localized, or the objects are going to be referenced (known) by multiple objects.

Roger Nelson