views:

176

answers:

5

I have a base class, Token. It has no implementation and as such acts as a marker interface. This is the type that will be used by callers.

{
    Token t = startJob(jobId);
    // ... (tasks)
    // t falls out of scope, destructors are called
}

I have a derived class, LockToken. It wraps around a mutex and insures the lock is acquired during construction and released during destruction. The startJob method is a factory method in the sense that it decides whether to return a Token (providing no locking) or a LockToken (providing locking).

Token startJob(int jobId)
{
    return (jobId>0) ? LockToken() : Token() ;
}

When startJob would return a base instance (a Token), everything works well. In the other case (jobId>0), there is a copy made of the derived instance to the base instance. In other workds, a different Token is copy-constructed from the LockToken and the original LockToken falls out of scope too soon, releasing the lock inside the scope of startJob.

How do I get out of this? Can I change startJob so that it returns or outputs a truly covariant Token (meaning it may be a LockToken)?

+5  A: 

You should return a pointer from startJob() - either a raw pointer or a suitable smart pointer. For example:

Token* startJob(int jobId) 
{ 
    return (jobId>0) ? new LockToken() : new Token(); 
} 

{ 
    std::auto_ptr<Token> t = startJob(jobId); 
    // ... (tasks) 
    // t falls out of scope, destructors are called 
}

When auto_ptr goes out of scope it calls delete on the wrapped pointer.

The above solution is a direct rewrite of yours. As mentioned in another answer to this question you don't need the dummy Token class at all - you can just return a null pointer:

LockToken* startJob(int jobId) 
{ 
    return (jobId>0) ? new LockToken() : 0; 
} 

{ 
    std::auto_ptr<LockToken> t = startJob(jobId); 
    // ... (tasks) 
    // t falls out of scope, destructors are called 
}

auto_ptr can be safely assigned a null pointer - it's destructor will handle this.

sharptooth
+1, or other smart pointers: `std::tr1::scoped_ptr`, `std::tr1::shared_ptr` (or their boost alternatives if your compiler does not support tr1)
David Rodríguez - dribeas
auto_ptr is indeed a very good idea. A better idea would have been for start_job to actually return such a type instead of a regular pointer... you know, just to be clear about the interface.
Matthieu M.
+2  A: 

One fairly typical approach would be to declare your Token / LockToken objects on the heap using pointers.

Token* startJob(int jobID)
{
    Token* t;
    if (jobID >0)
    {
        t = new LockToken();
    }
    else
    {
        t = new Token();
    }

    return t;
}

Of course, then you have to be responsible for deleting the returned value when you're done with it. Alternatively, you could use smart pointers, which manage their own destruction.

csj
+9  A: 

You are returning a Token by value. That means that you are not returning a LockToken, but rather a Token copy constructed from your LockToken instance.

A much better approach would be to use boost::shared_ptr. That way your clients can copy things around without needing to worry about deletion. Something like this:

#include <boost/shared_ptr.hpp>

typedef boost::shared_ptr<void> Token;

Token startJob(int jobId)
{
    if (jobId < 1) return shared_ptr<void>();
    return shared_ptr<void>(new LockToken);
}

void use_it()
{
    Token t = startJob(jobId);
    // ....
    // Destructors are called
}

Note that you no longer need a Token class that does nothing, and the LockToken class is now an implementation detail that is entirely hidden from clients, giving you scope for doing all kinds of other things when the Token goes out of scope.

janm
No, shared_ptr<void>. There is no need to even have a class called Token if it doesn't do anything.
janm
Anyway, you should edit the return of the function to match what is actually return, as well as the type of 't' in 'use_it'...
Matthieu M.
@Matthieu: See the typedef at the top of the example. The point of the question is: "How can I return a token that does different things on destruction depending on other constraints". Answer: "provide an opaque handle and shared_ptr<> is a good way to do that." The function returns a shared_ptr<void>; there is no need to expose the fact that it contains a pointer to a LockToken, that is an implementation detail. Something like: "return shared_ptr<void>(static_cast<void*>(0), bind(myfunc, _1))" would also be acceptable and would not change the interface provided.
janm
+1  A: 

In C++, to get polymophic behavior you need to use either pointers or references. In your particular case, as the lifetime of the Token must expand beyond the function startJob you cannot return a reference into an internal stack allocated object, since at the place of use (caller of startJob) it would be a dangling reference.

So you are left with dynamically allocated memory, and at this point you can choose how you want to deal with the heap allocated object lifetime. I would advise against using raw pointers as they are inherently exception unsafe, there are already different fine answers using raw pointers as return value and managing the pointer inside a smart pointer, or already returning an smart pointer.

The disadvantage of returning a raw pointer and externally managing it in a smart pointer is that it is a little more fragile for user code. The caller can use smart pointers, or can use raw pointers (or ignore the returned object) and it will loose memory. Using a shared_ptr in your user interface imposes the use of that smart pointer in caller code (the user cannot decide to change to another smart pointer type).

Using a good old std::auto_ptr as return type seems like the most flexible approach at this point:

std::auto_ptr<Token> startJob( int jobId );

void user_code()
{
   std::auto_ptr<Token> job1 = startJob(1);
   boost::shared_ptr<Token> job2( startJob(2) ); // shared_ptr has a constructor taking auto_ptr
   startJob(3); // fine: the temporary auto_ptr dies and releases the memory
   boost::scoped_ptr<Token> job4( startJob(4).release() ); // cumbersome, but feasible
}

(scoped_ptr reference)

If the returned type was another type of smart pointer as the return type, then you would not be able to make it yield the resource to use in another type of smart pointer. If you returned a raw pointer job 3 token will not be released.

I have not considered unique_ptr for the discussion. It seems as a good alternative to auto_ptr, but I have never used it so I cannot tell from experience.

David Rodríguez - dribeas
"In C++, to get polymophic behavior you need to use either pointers or references" and "So you are left with dynamically allocated memory". I don't agree with either of these things, at least as far as the interface is concerned. Assuming a function that does something based on a job_id, you could use something like "shared_ptr<void>(reinterpret_cast<void*>(jobId), bind(release_func, _1))". No pointers, no references, no explicit dynamically allocated memory (ignoring shared_ptr<> implementation details).
janm
In this specific case, returning a class by value containing some context, a function pointer and a destructor could also provide the same functionality, but why bother when shared_ptr<> gives you and the client much more flexibility?
janm
@janm: 'polymorphic behavior' cannot be achieved without a pointer or reference to a base class. You can store a void pointer is you wish, but you will not be able to call any method on it. I would not call dispatching in terms of a handler (jobId) polymorphic either.
David Rodríguez - dribeas
@janm: 'dynamically allocated memory': You do need dynamically allocated memory in this case (as compared to 'on-stack-objects' from the question's title). In your own answer you _are_ creating the object in the heap and storing it in a pointer (call it shared_ptr). The question explicitly talks about 'on-stack objects', and the answer is _no_, you cannot do it with stack allocated objects, you _do_ need heap allocated objects.
David Rodríguez - dribeas
We're not talking about the same thing. But just in case: When the last reference to a shared_ptr<void> goes out of scope when it was created with "shared_ptr<void>(new Object)", the destructor for Object will be called. In this case we are talking about a handle where we only care about scope, and the only method being called is the destructor and it is in that context that I was talking about polymorphinc behaviour. And in that context, there is no need to expose references or pointer in the interface to a function like this.
janm
The example of "shared_ptr<void>(reinterpret_cast<void*>(jobId), bind(release_func, _1))" does not dispatch through a void pointer. When the share_ptr goes out of scope, release_func is called with the jobId as an argument (in a void*). The pointer value held by a shared_ptr does not need to refer to heap allocated storage.
janm
"On stack" from the question is irrelevant to the actual problem. You could have a class that contains context information, a function pointer and handles transfer of ownership in a similar way to auto_ptr (with all the problems that entails) (or: have the caller pass a pointer to his own copy of the object so that copying is not required). The reason for heap allocation is to manage reference counting when clients are allowed to copy instances, which gives clients more flexibility and hides more of the implementation, also giving the implementer more flexibility.
janm
The core issue I missed from the initial read of the question is that the only part of the interface that is needed is the destructor itself. This means that wrapping the _dynamically allocated_ object within a shared\_ptr<void> keeps enough interface (the correct destructor will be called).
David Rodríguez - dribeas
If you need/want to keep a hierarchy and return different types from within the function then dynamic allocation is not optional but required. Of course you can define a class just for this purpose that controls whether or not a lock should be acquired/released and return an instance of that class properly initialized, but that is out of the scope of the question.
David Rodríguez - dribeas
OK. The hierarchy and different return types was the original poster's attempt to solve the problem, not the actual requirement. Recall that the base "Token" class was empty and only existed for the purpose of specifying the interface.
janm
"This means that wrapping the dynamically allocated object ..."The shared_ptr<void> does not need to wrap a dynamically allocated object, assuming the objective is to call a function with a given "jobId" as a parameter. I agree that we don't have enough information to know whether that is true in this case, but it is, in principle, possible.
janm
A: 

Thanks for all the replies! I have chosen to go with janm's solution with the exception that I will be using a std::auto_ptr instead of a boost::shared_ptr. Note that this makes the solution also similar to sharptooth's answer (with Matthieu M's comment). I have prototyped it and also integrated it with the rest of the application and am happy to report that it works well.

kiwicptn