15

Let's say I have a class:

class Scheduler {
    Scheduler(JobService *service);
    AddJob(JobID id, ISchedule *schedule);
}

The constructor takes a pointer to the service, but Scheduler does not take ownership of the service pointer. Service pointer is assumed to be released by the caller.

AddJob case is the opposite. Schedule lifetime is managed by the scheduler and when job is no longer needs to run schedule memory is released.

From the API point of view it is not clear who takes ownership of the pointer and who does not. I wounder if there are some techniques to indicate the intent through API design and not through documentation. To make it a bit more fool proof and obvious.

If I could, I would construct instance of ISchedule, but it is an abstract class in C++ (interface) and thus it would not be practical to create Add overloads for each type of schedule. So, I have to take a pointer in Add.

user0042
  • 7,917
  • 3
  • 24
  • 39
Sergei G
  • 1,561
  • 3
  • 18
  • 26
  • 4
    If you are using C++11 or later, change `AddJob() ` to take a `std::unique_ptr` or `std::shared_ptr` as input, then there is no question about ownership – Remy Lebeau Jul 13 '17 at 18:01
  • @Remy Do we really need to refer to outdated C++ standards? – user0042 Jul 13 '17 at 18:04
  • 4
    @user0042 yes. A lot of people are still not using C++11 yet. – Remy Lebeau Jul 13 '17 at 18:05
  • @remy Well, schools in rural areas of north-india for example, it seems. – user0042 Jul 13 '17 at 18:10
  • 1
    `template void AddJob(JobID, Ts&&...)` would allow to construct `Schedule` from `Scheduler`. – Jarod42 Jul 13 '17 at 18:19
  • If, for some reason, a smart pointer is not an option you might be interested in `owner<>` introduced in the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) available in the [Guideline Support Library](https://github.com/Microsoft/GSL) – Chris Drew Jul 13 '17 at 18:43
  • 2
    embedded software development is tricky when it comes to standards support; don't judge. – Sergei G Feb 14 '19 at 20:51

3 Answers3

13

The number of scenarios is larger than just two.

class Scheduler {

    // pass the raw pointer (or use a reference) to expresses
    // no ownership transfer (The passed in object will no longer be  
    // needed after the pointer or reference becomes invalid)
    Scheduler(JobService* service); 
    Scheduler(JobService& service); 

    // use a std::unique_ptr to pass ownership
    AddJob(JobID id, std::unique_ptr<ISchedule> schedule);

    // use a std::shared_ptr to pass shared ownership
    // when the passed in object needs to outlive either the caller
    // or the receiver and either one may need to delete it
    SomethingElse1(std::shared_ptr<Stuff> stuff);


    // use a std::weak_ptr to pass shared ownership
    // when the object may, or may not outlive
    // the receiver and the receiver needs to be able to detect
    // if the pointer is still valid (like an intermittent service)
    SomethingElse2(std::weak_ptr<Stuff> stuff);
};

References:

R.30 Take smart pointers as parameters only to explicitly express lifetime semantics

R.32 Take a unique_ptr parameter to express that a function assumes ownership of a widget

R.34 Take a shared_ptr parameter to express that a function is part owner

Galik
  • 47,303
  • 4
  • 80
  • 117
  • *"(This object dies before the pointer becomes invalid)"*. it depends:`Scheduler` can just use content of `JobService`, and then service can die immediately; Or it can store its reference and then adding lifetime constraints. – Jarod42 Jul 13 '17 at 18:30
  • I even forgot to mention plain references are another alternative. – user0042 Jul 13 '17 at 18:31
  • @Jarod42 Yes, I was assuming passing ownership to the object, not the function (I think that is implied in the question) but I changed the wording to cover both. – Galik Jul 13 '17 at 18:46
7

You don't have any options (other than clear documentation), to indicate the ownership of a raw pointer.

That's what the smart pointers from the c++ dynamic management library are for:

  • std::unique_ptr passes ownership to the receiver
  • std::shared_ptr shares ownership amongst holders
  • std::weak_ptr indicates a dependent share

And as pointed out in @Galik's brilliant answer a dedicated reference could be used to indicate strict lifetime dependency.

user0042
  • 7,917
  • 3
  • 24
  • 39
  • And [`std::experimental::observer_ptr`](http://en.cppreference.com/w/cpp/experimental/observer_ptr) to be explicit for no-ownership. – Jarod42 Jul 13 '17 at 18:15
  • @Jarod Well, that's listed under _experimental_ so far. – user0042 Jul 13 '17 at 18:17
  • I mean that no-ownership can also have its class to be explicit (experimental, home-maid, ...). As regular pointer can be "misused". – Jarod42 Jul 13 '17 at 18:25
  • /OT @Jarod42 _"home-maid"_ I'd love to have one here cleaning up all the mess day for day ;-) – user0042 Jul 13 '17 at 18:35
2

Passing std::unique_ptr<ISchedule> is an idiomatic way of transferring ownership of an object. So this is a right way to go for AddJob.

Passing a raw pointer indicates that no ownership is transferred.

Obviously std::shared_ptr indicates ownership sharing.

Anton Petrov
  • 782
  • 1
  • 8
  • 19