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

not_null no longer requires a copyable pointer type #675

Closed
wants to merge 5 commits into from

Conversation

ericLemanissier
Copy link
Contributor

allows support of supporting std::unique_ptr
based on @xaxxon's work

fixes #89
fixes #550
fixes #651

From the Core Guidelines:

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#note-61

Note not_null is not just for built-in pointers. It works for unique_ptr, shared_ptr, and other pointer-like types.

@ericLemanissier ericLemanissier force-pushed the patch-2 branch 2 times, most recently from ceb8d64 to bdf1bcc Compare May 2, 2018 18:32
allows support of supporting std::unique_ptr
based on @xaxxon's work

fixes microsoft#89
fixes microsoft#550
fixes microsoft#651
@annagrin
Copy link

@ericLemanissier thanks! That looks clean and separate from the get() function change. How does
not_null<unique_ptr<T>> behave when copied, moved, assigned, reset? If would be great to add tests to ensure right things compile and wrong do not (you can #define the non-compiling tests away). Also, adding tests to get the value out of the unique_ptr would be useful as well.

@ericLemanissier
Copy link
Contributor Author

@annagrin I added to tests to make sure not_null<unique_ptr> is move constructible, move assignable, non copyable. Checking that the moved from not_null actually became null after move is currently not possible, because it triggers postcondition error in not_null::get, so I put this part of the test inside #ifdef GSL_THROW_ON_CONTRACT_VIOLATION. It could be simplified if #674 is merged

using NotNull1 = not_null<std::unique_ptr<UniquePointerTestStruct>>;

// values are the same
CHECK((NotNull1(std::make_unique<UniquePointerTestStruct>())->i == NotNull1(std::make_unique<UniquePointerTestStruct>())->i));

Choose a reason for hiding this comment

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

Would be nice also to check the value of field i

CHECK(*dst.get() == 42);
src = std::move(dst);
#ifdef GSL_THROW_ON_CONTRACT_VIOLATION
CHECK_THROWS_AS(!dst.get(), fail_fast);

Choose a reason for hiding this comment

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

can we show that reset() and setting to nullptr do not compile?

// values are the same
CHECK((NotNull1(std::make_unique<UniquePointerTestStruct>())->i == NotNull1(std::make_unique<UniquePointerTestStruct>())->i));
}
}

Choose a reason for hiding this comment

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

would be great to see how does this change behaves with other smart pointers

@annagrin
Copy link

@ericLemanissier please ignore deleted comment, I misused unique_ptr.
Would love to see more tests for getting fields out of not_null<unique_ptr<CustomPointer>> , tests showing that reset() does not compile, and tests for other smart pointers.

BTW, looks like your other change - removing Ensures() from get() - is incompatible with this one, as it will remove the null checking on moved not_null<unique_ptr<X>>

int main()
{
    not_null<std::unique_ptr<int>> y{ std::make_unique<int>() };
    auto z = std::move(y);
    return *y; // AV here
}

also, declared not_null move assignment as defaulted
@ericLemanissier
Copy link
Contributor Author

@annagrin Are these the tests you had in mind ?
Regarding the interaction with #674, it is limited to changing the blocks

#ifdef GSL_THROW_ON_CONTRACT_VIOLATION
    CHECK_THROWS_AS(!src.get(), fail_fast);
#endif

to

    CHECK(!src.get());

@ericLemanissier
Copy link
Contributor Author

@annagrin what do you think of this PR ?

@annagrin
Copy link

annagrin commented Jun 15, 2018

@ericLemanissier I would like to clear some points with this PR and #674.

  1. A conceptual problem - before this change you could not have a variable of not_null<T> containing a null pointer (and that would allow for remove null check during access #674 to be possible). Now notnull<unique_ptr<T>> might contain a null pointer if unique pointer was reset. We could argue that the relaxed semantics makes more sense, but that should be a conscious choice, and argument actually happening... I would love to gather more opinions on that.

  2. Corollary of 1 - problem with this change being incompatible with remove null check during access #674 - together they remove termination/exception in the example I provided before, and allow an actual null dereference, which is going against the purpose of not_null type.

@ericLemanissier
Copy link
Contributor Author

A mitigation of the problem is that get returns a reference to const, so the unique_ptr cannot be reset, unless explicitely casting const away, which is not a good pratice.

@gdr-at-ms
Copy link
Member

If you move from not_null<unique_ptr<T>>, what happens?

@ericLemanissier
Copy link
Contributor Author

The moved from not_null contains an empty unique_ptr, so it is actually null. This is not a problem if the next operation is destruction, or assignment, which is a usual behaviour for moved from objects, and not surprising because it has the same behaviour as the class template argument type.
One might argue that all other member functions(get and all operators calling get) documentation should be explicit about the requirement for this to be in a non moved from state. Any way, such missuses are already caught by the ensures clause. By the way, shouldn't it be an expects clause instead?

@annagrin
Copy link

annagrin commented Jul 3, 2018

@gdr-at-ms I could use your opinion on the semantic change - see my "1. Conceptual problem" above,

@ericLemanissier If we accept this change, we would have access violations after move if #674 is checked in as well. So my inclination is to take this change but not the #674.

@KVic
Copy link

KVic commented Jul 4, 2018

@gdr-at-ms, @annagrin, what happens?

#include <gsl/pointers>

class SomeClass {};

int main()
{
    using namespace std;

    gsl::not_null<unique_ptr<SomeClass>> some_unique{make_unique<SomeClass>()};

    SomeClass some_class = move(*some_unique);

    return 0;
}

So I agree with @ericLemanissier that "This is not a problem if the next operation is destruction, or assignment, which is a usual behaviour for moved from objects". And there is no need in the Ensures() check in the get() in the Release build.

@annagrin
Copy link

annagrin commented Apr 8, 2019

Thanks @ericLemanissier , I am merging this change, but would need to reject the #674 - we cannot remove the null check now, because moved-from not-null smart pointer can now contain a null pointer.

@JordanMaples
Copy link
Contributor

Maintainer's call: I've opened issue isocpp/CppCoreGuidelines#1499 to continue the discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
5 participants