Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move support for API classes #199

Closed
wants to merge 19 commits into from
Closed

Move support for API classes #199

wants to merge 19 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Apr 16, 2018

This PR address ticket #642

I used the default keyword for implementation of move ctor and assignment. Currently copy ctor and assignment op have user defined, as it is necessary for pre c++11. I also thought this doesn't require us to include <utility> to use std::move fn(not sure whether this point is really useful). Let me know whether this is fine.

Please review when you have time. Thanks!

Can you comment on the commit boundaries I formed? Just curious to know whether I can improve on that.

@@ -54,6 +54,7 @@
%ignore NS::CLASS::CLASS(Internal*);
%ignore NS::CLASS::CLASS(Internal&);
%ignore NS::CLASS::operator=;
%ignore NS::CLASS::CLASS(CLASS &&);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this change, I get the following error.

 Warning 509: Overloaded method Xapian::Document::Document(Xapian::Document const &) effectively ignored,
Warning 509: as it is shadowed by Xapian::Document::Document(Xapian::Document &&).

I don't know much about SWIG. From what's already there, I added this fix. Please tell whether this is the way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems appropriate to me - these objects are reference counted handles, so it's not useful to explicitly wrap ways to create a copy in the target language.

For some reason Tcl needs the copy ctor wrapped. I'm not sure we bothered to work out why, but it shouldn't need the move ctor since it doesn't currently have it.

@ghost ghost changed the title Move support for API classes having intrusive_ptr_nonnull as member Move support for API classes having only smart pointer as member Apr 16, 2018
PositionIterator::PositionIterator(PositionIterator &&) = default;
PositionIterator::PositionIterator(PositionIterator && o)
: internal(o.internal)
{
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I add LOG same as copy ctor & assignment op(except object pointer)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think logging is all that useful for these. The compiler is allowed (and sometimes required) to eliminate calls to them, so logging wouldn't correspond to the source exactly anyway.

The only reason I can think that logging might be interesting is to try to find where copies aren't eliminated to try to address that. But by inlining we open things up for the compiler to optimise in more ways, and without the programmer having to study logs. The savings aren't likely to be very large anyway, as copying just copies a pointer and adjusts reference counts.

Overall having these inline in the API headers seems a better trade-off than putting them into the library so they can have debug logging.

guruhegde added 2 commits April 16, 2018 22:23
Member of iterator class is raw pointer, as a result compiler
generated move ctor and assignment leads to memory leak,
Hence providing user implementation.
{
if (this != &o)
swap(internal, o.internal);
RETURN(*this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just be return *this; - only use RETURN if there's debug logging of the function/method call (i.e. LOGCALL() or similar).

(These methods aren't really interesting to log, so I think it's fine not to have logging for them.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

: internal(o.internal)
{
o.internal = nullptr;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be OK to have inline in the public API header (include/xapian/positioniterator.h).

That's beneficial for a trivial implementation like this one as it means that the compiler can inline it and optimise the result. It also means we don't create another external symbol in the shared library (the fewer external symbols there are, the quicker it loads). One extra isn't a big deal by itself, but where they can be easily avoided we try to.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip. Done.

PositionIterator::operator=(PositionIterator && o)
{
if (this != &o)
swap(internal, o.internal);
Copy link
Contributor

@ojwb ojwb Apr 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably also a candidate for having inline in the public API header (and then the compiler should usually be able to at least optimise away the (this != &o) check entirely as it can typically see that the two objects are distinct).

If we do that I'm not sure about the use of std::swap() though - we don't currently pull in <utility> from public headers and the fewer library headers we include from public headers the better in many ways - user code can end up implicitly relying on our including them so removing them later risks breaking such user code. So perhaps it would be better to write it out in this case (auto t = internal; internal = o.internal; o.internal = t;).

Or alternatively leave o with NULL internals:

    if (this != &o) {
        if (internal) decref();
        internal = o.internal;
        o.internal = nullptr;
    }

(And similarly for the other iterator classes...)

@@ -29,6 +29,7 @@

#include <iosfwd>
#include <string>
#include <utility>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so we'll need <utility> for std::move anyway.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use static_cast to avoid it. should I go for that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I guess it is not a straightforward cast. I have to understand it better first.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom impl of move is not trivial it seems. Please ignore my comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast would be static_cast<PositionIterator&&>(obj) (when obj's class is PositionIterator).

I think that's worse than just including <utility> though - the code is much clearer using std::move().

/// Move constructor.
WritableDatabase(WritableDatabase&& o) : Database(std::move(o)) {}

/// Move Assignment operator.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason to capitalise "Assignment" here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@ojwb
Copy link
Contributor

ojwb commented Apr 17, 2018

I used the default keyword for implementation of move ctor and assignment. Currently copy ctor and assignment op have user defined, as it is necessary for pre c++11. I also thought this doesn't require us to include to use std::move fn(not sure whether this point is really useful). Let me know whether this is fine.

I think default is better - it's shorter, clearer, and there's no scope for a slightly incorrect attempt at a default implementation. It'd be good to change the default-equivalent implementations now that we require C++11 in both the library and public headers.

Avoiding including <utility> from the public API headers would be a bonus, but I think it's not something we can easily avoid entirely.

It'd also be good to switch to using delete where we currently just make them private as that helps the compiler give better error messages.

Can you comment on the commit boundaries I formed? Just curious to know whether I can improve on that.

I just looked through the combined diff so far. I really should get on with something else right now, but I'll have a look when I get a chance.

guruhegde added 7 commits April 18, 2018 12:37
This is beneficial because compiler can inline it
and optimise the result. Also this means, less external
symbol in the shared library hence loads quicker.
It is beneficial because compiler can inline it and
optimise the result. Also this means, less external
symbol in the shared library, so loads quicker.

The implementation is chosen to avoid swap so custom
swap impl or  <utility> header inclusion not required.
It's short, clear and error-safe for default implementation.
Explicitly defaulted function requires return type as
reference in function signature.
@ghost
Copy link
Author

ghost commented Apr 20, 2018

I think most of the classes in public API covered. Let me know if any other work is pending on this.

@ghost ghost changed the title Move support for API classes having only smart pointer as member Move support for API classes Apr 24, 2018
@ojwb
Copy link
Contributor

ojwb commented Jun 6, 2018

Sorry getting back to this now.

The newer changes look good to me, will merge this now.

Can you comment on the commit boundaries I formed? Just curious to know whether I can improve on that.

They look great to me.

@ojwb
Copy link
Contributor

ojwb commented Jun 6, 2018

git rebase -i --autosquash-ed and merged to master in 32a7fd6.

Thanks for all your work on this.

@ojwb ojwb closed this Jun 6, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant