--
--
Chromium Developers mailing list: chromi...@chromium.org
View archives, change email options, or unsubscribe:
http://groups.google.com/a/chromium.org/group/chromium-dev
You received this message because you are subscribed to the Google Groups "Chromium-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-dev...@chromium.org.
(3) used to be better than (1) when f() took a reference as it avoided an extra reference for the temporary argument.
Now that we have move semantics though I guess (1) is always preferable to (3) as it allows a zero reference bump flow when the caller is giving up ownership via std::move (and f() is also taking it via std::move as well).
With (3) there will always be at least one reference bump when handing ownership as std::move isn't allowed on const objects.
(Note: I'm not an expert on move semantics, please let me know if I missed something there)
Gab
Agreed that we should use scoped_ptr over scoped_refptr as much as possible, but I think this discussion is about the style recommandation for scoped_refptr which with move semantics can now actually be the same as scoped_ptr I think.Do we agree that for both scoped_ptr and scoped_refptr we should do (1) when f() is allowed to take ownership and (2) when it isn't (and definitely *never* do (2) with a "takes ownership" comment: putting a raw pointer in a managed pointer is always wrong except on allocation IMO).
PK
On Thu, Feb 18, 2016 at 11:58 AM, Peter Kasting <pkas...@chromium.org> wrote:On Thu, Feb 18, 2016 at 11:49 AM, Gabriel Charette <g...@chromium.org> wrote:Agreed that we should use scoped_ptr over scoped_refptr as much as possible, but I think this discussion is about the style recommandation for scoped_refptr which with move semantics can now actually be the same as scoped_ptr I think.Do we agree that for both scoped_ptr and scoped_refptr we should do (1) when f() is allowed to take ownership and (2) when it isn't (and definitely *never* do (2) with a "takes ownership" comment: putting a raw pointer in a managed pointer is always wrong except on allocation IMO).We should always do (1) when a function is to take ownership. But I'm not sure we should command (2) in other cases. I actually sort of like that (3) explicitly says "someone else owns me". There's no ambiguity, as there can be with a raw pointer.(3) would pretty much never be appropriate for scoped_ptr,
PK
PK
On Thu, Feb 18, 2016 at 12:55 PM, Antoine Labour <pi...@google.com> wrote:On Thu, Feb 18, 2016 at 12:44 PM, Peter Kasting <pkas...@chromium.org> wrote:On Thu, Feb 18, 2016 at 12:40 PM, Antoine Labour <pi...@google.com> wrote:On Thu, Feb 18, 2016 at 11:58 AM, Peter Kasting <pkas...@chromium.org> wrote:On Thu, Feb 18, 2016 at 11:49 AM, Gabriel Charette <g...@chromium.org> wrote:Agreed that we should use scoped_ptr over scoped_refptr as much as possible, but I think this discussion is about the style recommandation for scoped_refptr which with move semantics can now actually be the same as scoped_ptr I think.Do we agree that for both scoped_ptr and scoped_refptr we should do (1) when f() is allowed to take ownership and (2) when it isn't (and definitely *never* do (2) with a "takes ownership" comment: putting a raw pointer in a managed pointer is always wrong except on allocation IMO).We should always do (1) when a function is to take ownership. But I'm not sure we should command (2) in other cases. I actually sort of like that (3) explicitly says "someone else owns me". There's no ambiguity, as there can be with a raw pointer.(3) would pretty much never be appropriate for scoped_ptr,Why?It seems like the pros and cons for scoped_ptr and shared_ptr here are similar.If all you have in an object is a T* because you don't take ownership, it is invalid to put that T* into a scoped_ptr<T>, so it is not appropriate for a function to require a const scoped_ptr<T>&.Not all functions which take T*s are signalling, by taking a T*, that they do not take ownership. We have a large number of functions in our codebase today which take ownership of raw pointers passed to them.Whereas, if something is passed in as const scoped_ptr&, it is extremely clear that the function _must not_ take ownership.
I'd love if ownership was only ever held by scoping objects and no functions could ever take ownership except of something they create locally or receive as a scoped_ptr, but that's not even close to true today. In the meantime, I don't think it's harmful to allow passing scoped_ptrs by const ref. I wouldn't require it, but I'm opposed to banning it.
PK
On Thu, Feb 18, 2016 at 1:38 PM, Peter Kasting <pkas...@chromium.org> wrote:Not all functions which take T*s are signalling, by taking a T*, that they do not take ownership. We have a large number of functions in our codebase today which take ownership of raw pointers passed to them.Whereas, if something is passed in as const scoped_ptr&, it is extremely clear that the function _must not_ take ownership.Sure, but the function is not usable in any case where the caller doesn't have ownership, or if the object is not heap-allocated. It's an anti-pattern... It's (fortunately) virtually non-existent in the codebase. I counted 42 such functions, where 5 of them are conceptually wrong (they use a const scoped_ptr& to receive a base::Passed value via base::Bind, so the intent is actually to indicate that they *do* take ownership). About half of the remaining ones are helper functions (typically in tests) that are called repeatedly on actual scoped_ptr fields, so they use const scoped_ptr& to save a .get() at the callsite (a legitimate use in my book). In one case, the const scoped_ptr& forced the caller to allocate the object on the heap instead of the stack.
I'd love if ownership was only ever held by scoping objects and no functions could ever take ownership except of something they create locally or receive as a scoped_ptr, but that's not even close to true today. In the meantime, I don't think it's harmful to allow passing scoped_ptrs by const ref. I wouldn't require it, but I'm opposed to banning it.I think we should aim towards fixing functions that take ownership of a T*, rather than introducing an inefficient and wordy workaround that limits reuse.
PK
I'm not signing up for removing const scoped_refptr& cases. There's ~4500 of them, and each of them requires careful understanding of the function to know whether it takes ownership (-> scoped_refptr<T> and fix callers to use std::move as appropriate) or not (-> (const) T*), let alone cases where it isn't statically decidable (in which case passing scoped_refptr<T> may cause perf regressions).
--
Just to recap the various differences between scoped_ptr and scoped_refptr:* Ownership semantics for base::RefCounted is a property of the type but for scoped_ptr semantics is a property of the pointer, e.g. you could release the object from a scoped_ptr and put it into an std::shared_ptr if you wanted.* For scoped_ptr we use std::move to transfer ownership but with base::RefCounted we either copy the scoped_refptr or create a new scoped_refptr for sharing ownership.* For scoped_refptr we can use const scoped_refptr<T>& (callee creates copy) or std::move (caller creates copy) to avoid extraneous ref counting but for scoped_ptr using const scoped_ptr<T>& is a means to avoid calling get() which should be inlined anyway.With that being said, I prefer:* Callee takes T* for indicating that ownership is neither being transferred nor shared. Caller guarantees that object is alive during the entirety of the call.* Callee takes scoped_ptr<T> for indicating that ownership is being transferred. Caller calls std::move here because scoped_ptr is a move-only type.* Callee takes scoped_refptr<T> for indicating that ownership is being shared. Caller calls std::move if ownership is being transferred and not if ownership is being shared. This also avoids extraneous ref counting.e.g. X::Create(scoped_refptr<Y> y) transfers ownership of y to X's constructor so it calls X(std::move(y)). But X::Create's caller usually only wants to share ownership so it calls X::Create(y).This scheme also has the advantage of working with std::shared_ptr if we ever migrate to using that.
With that being said, I prefer:* Callee takes T* for indicating that ownership is neither being transferred nor shared. Caller guarantees that object is alive during the entirety of the call.* Callee takes scoped_ptr<T> for indicating that ownership is being transferred. Caller calls std::move here because scoped_ptr is a move-only type.* Callee takes scoped_refptr<T> for indicating that ownership is being shared. Caller calls std::move if ownership is being transferred and not if ownership is being shared. This also avoids extraneous ref counting.e.g. X::Create(scoped_refptr<Y> y) transfers ownership of y to X's constructor so it calls X(std::move(y)). But X::Create's caller usually only wants to share ownership so it calls X::Create(y).
On Thu, Feb 18, 2016 at 7:42 PM Peter Kasting <pkas...@chromium.org> wrote:On Thu, Feb 18, 2016 at 4:18 PM, Sunny Sachanandani <sun...@chromium.org> wrote:With that being said, I prefer:* Callee takes T* for indicating that ownership is neither being transferred nor shared. Caller guarantees that object is alive during the entirety of the call.* Callee takes scoped_ptr<T> for indicating that ownership is being transferred. Caller calls std::move here because scoped_ptr is a move-only type.* Callee takes scoped_refptr<T> for indicating that ownership is being shared. Caller calls std::move if ownership is being transferred and not if ownership is being shared. This also avoids extraneous ref counting.e.g. X::Create(scoped_refptr<Y> y) transfers ownership of y to X's constructor so it calls X(std::move(y)). But X::Create's caller usually only wants to share ownership so it calls X::Create(y).Here's my attempt at restating this in an imperative form for use in one of our guides.***Object ownership and calling conventionsWhen functions need to take raw or smart pointers as parameters, use the following conventions. Here we refer to the parameter type as T and name as |t|.
- If the function does not modify |t|'s ownership, declare the param as T*. The caller is expected to ensure |t| stays alive as long as necessary, generally through the duration of the call. Exception: In rare cases (e.g. using lambdas with STL algorithms over containers of scoped_ptrs), you may be forced to declare the param as const scoped_ptr<T>&. Do this only when required.
- If the function takes ownership of a non-refcounted object, declare the param as scoped_ptr<T>.
- If the function takes a ref on a refcounted object, declare the param as scoped_refptr<T>. The caller can decide whether it wishes to transfer ownership (by calling std::move(t) when passing |t|) or retain its ref (by simply passing |t| directly).
In short, functions should never take ownership of parameters passed as raw pointers, and there should rarely be a need to pass smart pointers by const ref.***Does this look good? Are there any reasons to make any exceptions for const scoped_refptr&?
Are there any reasons to make any exceptions for const scoped_refptr&?
PK
David
Antoine
David
The last bullet perhaps needs to be more contrastful : s/takes a ref/may take a ref/ ?
Should the last bullet also explicitly state that "the function should call std::move(t) to take ownership". Otherwise people could respect the rule but still incur an unnecessary ref bump via the temporary argument if its ref is copied instead of taken.
On Fri, Feb 19, 2016 at 11:14 AM, Gabriel Charette <g...@chromium.org> wrote:The last bullet perhaps needs to be more contrastful : s/takes a ref/may take a ref/ ?That seems to make the text more vague and confusing. What do you mean by "may take a ref"? To me this reads the same as my rule + the (unwanted) extra proviso "or you can pass as scoped_refptr<T> if maybe someday in the future someone might want to take a ref".
Should the last bullet also explicitly state that "the function should call std::move(t) to take ownership". Otherwise people could respect the rule but still incur an unnecessary ref bump via the temporary argument if its ref is copied instead of taken.I don't think this is the right place for such guidance, as it's not really about function parameter declarations, and more just a general-purpose "how do I use std::move with scoped_refptr" tip, to be applied anywhere applicable in the codebase. I'd like to keep this text narrowly focused on just the one topic.
PK
Antoine
David
I have updated the coding style guidelines with approximately the text suggested. I also tweaked the smart pointer guidelines doc a bit.What remains to be done here:* Remove const scoped_ptr<T>& wherever possible.
* Remove const scoped_refptr<T>& wherever possible.
* Make scoped_refptr<T>::scoped_refptr(T*) explicit.
Thanks a lot Peter!I filed the bugs (noted in-line below), although I currently cannot commit to fixing them.
On Tue, Feb 23, 2016 at 2:46 AM, Václav Brožek <va...@chromium.org> wrote:Thanks a lot Peter!I filed the bugs (noted in-line below), although I currently cannot commit to fixing them.Thanks for this.I did get private mail from a non-committer expressing interest in doing some of this work. That seems like a good prompt for me to note that:(1) Anyone is free to tackle bits of the issues below, even if you don't fix the whole codebase. Incremental improvements are always welcome.(2) Mechanical changes like this can be a good opportunity for non-committers, in particular, to get some experience with the contribution process on the way towards requesting commit rights. Take your time and make sure the changes you make are the right ones -- we've had problems in the past with zealous but inexperienced contributors sending in fixes for these sorts of issues that actually introduce bugs -- and this can be a stepping stone to more substantive changes.
On Tue, Feb 23, 2016 at 3:00 AM Peter Kasting <pkas...@chromium.org> wrote:On Tue, Feb 23, 2016 at 2:46 AM, Václav Brožek <va...@chromium.org> wrote:Thanks a lot Peter!I filed the bugs (noted in-line below), although I currently cannot commit to fixing them.Thanks for this.I did get private mail from a non-committer expressing interest in doing some of this work. That seems like a good prompt for me to note that:(1) Anyone is free to tackle bits of the issues below, even if you don't fix the whole codebase. Incremental improvements are always welcome.(2) Mechanical changes like this can be a good opportunity for non-committers, in particular, to get some experience with the contribution process on the way towards requesting commit rights. Take your time and make sure the changes you make are the right ones -- we've had problems in the past with zealous but inexperienced contributors sending in fixes for these sorts of issues that actually introduce bugs -- and this can be a stepping stone to more substantive changes.On a related note, is the GoodFirstBug label still in use? Seems like that would apply here.
--
The Smart Pointer Guidelines contain a few sections that look quite related to the updated code style rules: When do we use each smart pointer, What are the calling conventions involving different kinds of pointers, and What about passing or returning a smart pointer by reference. Should the two possibly be merged or linked together?I also wonder whether all of the guidelines are still completely valid. For instance, they generally discourage the usage of scoped_refptr<>. As far as I could tell, that argument hasn't been mentioned throughout this thread. Does it still hold?
The Smart Pointer Guidelines contain a few sections that look quite related to the updated code style rules: When do we use each smart pointer, What are the calling conventions involving different kinds of pointers, and What about passing or returning a smart pointer by reference. Should the two possibly be merged or linked together?
I also wonder whether all of the guidelines are still completely valid. For instance, they generally discourage the usage of scoped_refptr<>. As far as I could tell, that argument hasn't been mentioned throughout this thread. Does it still hold?