Merge lp:~pete-woods/unity-api/add-glib-assigner-class into lp:unity-api

Proposed by Pete Woods
Status: Merged
Approved by: Pete Woods
Approved revision: 296
Merged at revision: 281
Proposed branch: lp:~pete-woods/unity-api/add-glib-assigner-class
Merge into: lp:unity-api
Prerequisite: lp:~pete-woods/unity-api/throw-for-unowned-types-lp1672657
Diff against target: 668 lines (+447/-43)
5 files modified
debian/changelog (+6/-0)
include/unity/util/GObjectMemory.h (+67/-6)
include/unity/util/GlibMemory.h (+142/-24)
test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp (+104/-13)
test/gtest/unity/util/GlibMemory/GlibMemory_test.cpp (+128/-0)
To merge this branch: bzr merge lp:~pete-woods/unity-api/add-glib-assigner-class
Reviewer Review Type Date Requested Status
Michi Henning (community) Approve
Unity8 CI Bot continuous-integration Approve
Alan Griffiths Abstain
Charles Kerr (community) Approve
Review via email: mp+320375@code.launchpad.net

Commit message

Add assigner class to support common GError usage pattern

Description of the change

Add assigner class to support common GError usage pattern as below:

GErrorUPtr error;
auto o = unique_glib(g_foobar(args..., GErrorAssigner(error)))
if (error)
{
    cerr << error->message << endl;
    return;
}

This avoids us having to manually free the error object, or having to mess around with an explicit temporary variable.

To post a comment you must log in.
Revision history for this message
Charles Kerr (charlesk) wrote :

LGTM.

review: Approve
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

I'm in two minds about this.

auto o = unique_glib(g_foobar(args..., GErrorAssigner(error)));

I don't like the need to explicitly use the GErrorAssigner here. With an implicit conversion to GError**, this would not be necessary.

I'm also not that keen on the macro hackery. I know, it's not your fault… :-)

How about having a class instead that is specific to GError and provides the implicit conversion?
That way, I can write

AutoGerror error;
auto o = g_foobar(args..., error);

Call the class AutoGError maybe? Or maybe GErrorGuard would be a better name?

Something along these lines:

http://pastebin.ubuntu.com/24219982

I think this would be easier to use and also easier to understand in terms of how it is implemented?

review: Needs Information
Revision history for this message
Michi Henning (michihenning) wrote :

And, oops, the test in operator GEError**() is redundant. It should be:

operator GError** noexcept
{
    g_clear_error(&err_);
    return &err_;
}

because g_clear_error() does check whether err_ or *err_ is NULL.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:279
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/156/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/4579
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/4607
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4434
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4434/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4434
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4434/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4434
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4434/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4434
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4434/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4434
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4434/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4434
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4434/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/156/rebuild

review: Approve (continuous-integration)
Revision history for this message
Pete Woods (pete-woods) wrote :

So one of the design goals I had for this was to use the built in unique_ptr and shared_ptr classes as much as possible:
* Developers are familiar with their semantics.
* unique_ptr "moves" to shared_ptr implicitly.

I agree it'd be nicer to try and make this a template, so I'll see if I can get that to work.

I plan to (if I can get it to work) make the technique also apply to glib style property getters (mainly for char arrays).

e.g. instead of:

gchar* a = nullptr;
gchar* b = nullptr;
g_get_properties(o, "a", &a, "b", &b, nullptr);
g_free(a);
g_free(b);

doing:

GCharUPtr a, b;
g_get_properties(o, "a", Assigner<gchar>(a), "b", Assigner<gchar>(b), nullptr);

I would also like it to apply generally to any glib or gobject type that is allocated this way.

e.g. a factory method:

unique_ptr<GApple> cpp_get_apple() {
    unique_ptr<GApple> a;
    if (!g_get_apple(_gFactory.get(), Assigner(a))) {
        throw some_error();
    }
    return a;
}

which I can then assign to a shared_ptr:

shared_ptr<GApple> a(cpp_get_apple());
other_method_call_that_wants_a_shared_ptr(a);

Revision history for this message
Pete Woods (pete-woods) wrote :

I also don't like the surprising automatic destruction of the wrapped object if someone decides to convert it to a double pointer. It makes me think of auto_ptr.

Revision history for this message
Michi Henning (michihenning) wrote :

It's difficult to get over all the friction here.

My gut feeling is that using the existing glib unique_ptr is a bit awkward. Not because there is anything wrong with the unique_ptr per se, but because the usage pattern here is different:

GError is conceptually a value type. The only reason it is dynamically allocated is because it is an out param that can be set to null to indicate "no error". So, the unique_ptr idea works well for return types, but I don't think it works all that well for out params.

I'd live with the unique_ptr thingy, if we could come up with a way to get rid of the Assigner. It really would be nice not to have to do that.

The destruction of the wrapped object isn't surprising, in my opinion. It simply deallocates any old value before possibly accepting a new value (which may be null). There has to be a way to easily re-used the same GError for several calls in a row, I think (whether a previous call succeeded or not). In effect, every time I pass a GError to a glib call, I know that I'll get a new value back, some error or a no error, with no possibility of leaking memory.

Do we need to support the T** conversion for potentially any and all glib pointer types, or just for some of them? I'm thinking that it might be possible to have a derived template that adds the conversion operator and then specialise that template for different glib types?

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:281
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/157/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/4598
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/4626
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4453
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4453/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4453
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4453/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4453
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4453/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4453
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4453/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4453
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4453/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4453
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4453/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/157/rebuild

review: Approve (continuous-integration)
Revision history for this message
Pete Woods (pete-woods) wrote :

I've put together the sort of thing I was talking about now. This includes the same sort of thing for both char array stuff and GObject instances.

If you can figure out a way to do the Glib side with derived templates I'm all ears. I struggled very muchly with the template instantiation order learning some new things on the way when I just tried to make the GlibAssigner a template instead of macro funkiness.

In terms of the deletion being surprising, let's give an example, assuming we've got a wrapper type that auto converts to **:

void do_stuff(Thingy** t) {}

auto t = unique_glib(new_thingy());
do_stuff(t);
// t is now empty!

I don't like that, and it really does feel like the same problem as the old auto_ptrs:

void do_stuff(auto_ptr<Thingy> t) {}

auto_ptr<Thingy> t(new Thingy());
do_stuff(t);
// t is now empty!

I also don't think you're putting across a concrete argument about why the "Assigners" are bad. Is it just because there's more typing? Can you express more clearly what the problem is, please.

I prefer them because the "wacky types that do magic stuff" only live very briefly for the duration of a function call. Instead of having smart pointers with unusual properties that live potentially a long time.

Revision history for this message
Michi Henning (michihenning) wrote :

> I've put together the sort of thing I was talking about now. This includes the
> same sort of thing for both char array stuff and GObject instances.
>
> If you can figure out a way to do the Glib side with derived templates I'm all
> ears. I struggled very muchly with the template instantiation order learning
> some new things on the way when I just tried to make the GlibAssigner a
> template instead of macro funkiness.

I'll have a look tomorrow and see whether I can come up with something.

> In terms of the deletion being surprising, let's give an example, assuming
> we've got a wrapper type that auto converts to **:
>
> void do_stuff(Thingy** t) {}
>
> auto t = unique_glib(new_thingy());
> do_stuff(t);
> // t is now empty!
>
> I don't like that, and it really does feel like the same problem as the old
> auto_ptrs:
>
> void do_stuff(auto_ptr<Thingy> t) {}
>
> auto_ptr<Thingy> t(new Thingy());
> do_stuff(t);
> // t is now empty!

Not sure there is anything surprising here at all. It's exactly the same as passing any value by non-const reference. When the call returns, the value may or may not have changed. I expect that: after all, I'm passing the value so it *can* be changed by the called function.

> I also don't think you're putting across a concrete argument about why the
> "Assigners" are bad. Is it just because there's more typing? Can you express
> more clearly what the problem is, please.

It feels a bit clunky to have to explicitly instantiate an assigner instance just so I can pass the error instance. I have to do this each and every time, and there is no other use case for the Assigner class, as far as I can see. The assigners aren't bad, but the usage looks a bit inelegant to me. Ideally, I'd like to have a type that is a straight drop-in replacement for GError and can be passed wherever GError** is expected by a glib function, without any extra noise.

> I prefer them because the "wacky types that do magic stuff" only live very
> briefly for the duration of a function call. Instead of having smart pointers
> with unusual properties that live potentially a long time.

The way I wrote AutoGError, I don't think there is any magic stuff. It behaves like a value type. The only thing I can do is ask it whether the value exists (operator bool()), and access the value (via operator-> and operator*). If there is no value, and I try to use it, I get undefined behavior, just as I do with unique_ptr. If I pass it "by reference" where a glib function expects a GError "by reference", I'm not going to be surprised if an old error is replaced with a newer one if I reuse the same error instance.

In essence, this boils down to the question "should out params be syntactically distinguishable at the point of call?" In C++, the answer is usually "no" because, when a parameter can be changed by a called function, it normally is passed by non-const reference. In C#, you have to explicitly decorate the parameter at the point of call instead. (I do have sympathy for both approaches.)

Revision history for this message
Pete Woods (pete-woods) wrote :

My point about the surprising thing is that even if the method you hand it over to does absolutely nothing, then the pointer is still freed.

Revision history for this message
Michi Henning (michihenning) wrote :

> My point about the surprising thing is that even if the method you hand it
> over to does absolutely nothing, then the pointer is still freed.

True. But the only time anything ever will call operator**() is when they make a glib function call. In that case, the error is either null already or it contains a pre-existing error. In the latter case, it *must* be released first (and the caller would have to do the same thing manually otherwise). Either way, there is no surprise here: a parameter is passed by "non-const reference" and, therefore, the value may differ once the call completes.

I hear you on this and, normally, I'd have the same opinion. I don't like spooky action at a distance. But the point of *this* exercise (not a general one) is to make the glib API safer; we are being dictated to by the existing glib API and have to work with that.

Revision history for this message
James Henstridge (jamesh) wrote :

Looking at the TypeName##Assigner class in GlibMemory.h, do we really need the UPtr here? It looks like all we really want is the deleter function, but that is tied up in the implementation of unique_glib(), since that's where the function is actually used in the expanded code.

Maybe the code could be simplified a bit by leaning in to the template code a bit more. If we instead define GlibDeleter something like this:

    template<typename T> struct GlibDeleter;

#define G_DEFINE_AUTOPTR_CLEANUP_FUNC(TypeName, func) \
    template<> struct GlibDeleter<TypeName> { \
        void operator()(TypeName* ptr) { \
            if (ptr != nullptr) { \
                func(ptr); \
            } \
        } \
     };

Now in the assigner class destructor, you could delete the pointer directly with GlibDeleter<TypeName>()(_ptr).

You could also define unique_glib/shared_glib outside of the macro expansion with something like:

template<typename T>
inline std::unique_ptr<T,GlibDeleter<T>> unique_glib(T* ptr
{
    return std::unique_ptr<T, GlibDeleter<T>>(ptr);
}

... which will only match types for which GlibDeleter<T> has a definition.

I suspect you could also use this to define a single templated Assigner class too.

Revision history for this message
James Henstridge (jamesh) wrote :

Never mind. I misunderstood what the purpose of the _uptr member was. It might have been a bit simpler to just use "_uptr.reset(ptr)" in the destructor though.

Revision history for this message
Michi Henning (michihenning) wrote :

OK, I'll live with the Assigner temporary at the call site. I used James's suggestions to do this with templates and find the the code more readable that way. Feel free to re-use (or not) as you see fit.

http://pastebin.ubuntu.com/24227239/

Revision history for this message
Pete Woods (pete-woods) wrote :

James, FYI doing the reset causes the deleter function to become invalid as far as I can tell. I don't quite understand this myself.

If you do e.g.:

unique_ptr<Foo, MyDeleter> o(nullptr, MyDeleter());
o.reset(new_foo()); // unique_ptr doesn't let you pass the deleter again here, presumably intentionally.

then unique_ptr causes a segfault on destruction.

Revision history for this message
Pete Woods (pete-woods) wrote :

@michi, aha! the thing I was missing was prototyping a templated class. For some reason I didn't know you could do this.

Revision history for this message
James Henstridge (jamesh) wrote :

Pete: IIRC, you can only set the deleter for a unique_ptr through its constructor. The reset() method changes the value but keeps the same deleter.

I suspect the problem you might have had is that in its current form, a default constructed GLibDeleter<type,delete_func> will segfault when used: the "_d" function pointer member won't have been initialised, so crashes when you try to call it.

See http://paste.ubuntu.com/24228221/ for example.

Revision history for this message
Pete Woods (pete-woods) wrote :

@michi thanks very much for spending the time on that implementation. I've tweaked it ever so slightly (and also prodded it quite a bit to try and understand all the tricks in it).

Revision history for this message
Pete Woods (pete-woods) wrote :

I'm just checking it for backwards compatibility with the only user at the moment, which is ubuntu-app-launch.

Revision history for this message
Pete Woods (pete-woods) wrote :

Following up on James's comment, though. The changed deleter type doesn't make unique_ptr get upset any more. So I've changed the call to unique_ptr to a .reset.

Revision history for this message
James Henstridge (jamesh) wrote :

One last suggestion: rather than having the user pick an assigner class, how about using a function to create assigner object, so that the type can be inferred?

Taking code from the tests, something like:

    gcharUPtr name;
    g_object_get(obj, "name", assign_to(name), nullptr);

    GErrorUPtr error;
    EXPECT_FALSE(g_key_file_get_boolean(gkf.get(), "group", "key", assign_to(error)));

This should be pretty easy if all the assigner classes are instantiations of the one template.

Revision history for this message
Pete Woods (pete-woods) wrote :

Seems that was a mistake. As the original unique_ptr may not have been initialized with the correct deleter, so for unique_ptr we need to use unique_glib and move.

For shared_ptr this isn't the case, though.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:282
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/159/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/4611
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/4639
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4466
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4466/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4466
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4466/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4466
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4466/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4466
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4466/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4466
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4466/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4466
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4466/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/159/rebuild

review: Approve (continuous-integration)
Revision history for this message
Pete Woods (pete-woods) wrote :

Have split out a shared_ptr and a unique_ptr version of the assigner. Feel free to tell me how to reduce the code complication between these two templates :)

Revision history for this message
Pete Woods (pete-woods) wrote :

code *duplication*

Revision history for this message
Pete Woods (pete-woods) wrote :

James, if that works will try and get it in now.

Revision history for this message
Pete Woods (pete-woods) wrote :

The challenge there, of course, is I will probably have to make the types movable so they can exit the function.

Revision history for this message
Pete Woods (pete-woods) wrote :

Okay, I've implemented James' suggestion now

Revision history for this message
Pete Woods (pete-woods) wrote :

The only thing really bothering me now is the dual implementations of shared and unique assigners. If you could conditionally compile different stuff into the deleter depending of if the template argument was a uptr or sptr then you could collapse this down. You'd also be able to collapse the two assign_sptr and assign_uptr methods at the same time.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:283
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/160/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/4616
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/4644
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4471
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4471/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4471
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4471/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4471
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4471/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4471
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4471/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4471
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4471/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4471
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4471/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/160/rebuild

review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> The only thing really bothering me now is the dual implementations of shared
> and unique assigners. If you could conditionally compile different stuff into
> the deleter depending of if the template argument was a uptr or sptr then you
> could collapse this down. You'd also be able to collapse the two assign_sptr
> and assign_uptr methods at the same time.

Function overloading?

    ~GObjectPtrAssigner()
    {
        if (_ptr)
        {
           do_stuff(_smart, _ptr);
        }
    }

    void do_stuff(std::shared_ptr<TypeName>& _smart, element* _ptr)
    {
       _smart.reset(_ptr, GlibDeleter<typename S::element_type>());
    }

    void do_stuff(std::unique_ptr<TypeName>& _smart, element* _ptr)
    {
        _smart.reset(_ptr, GObjectDeleter());
    }

review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Oops, didn't mean to vote

review: Abstain
Revision history for this message
Pete Woods (pete-woods) wrote :

No worries. That's a great idea. I'll see if I can get it working.

Revision history for this message
Michi Henning (michihenning) wrote :

> Function overloading?

This works, but it means that each instance has two do_stuff() member functions, only one of which will ever be called, and the other one is dead code. (Admittedly, very little code.)

Instead, we can use SFINAE:

    ~GlibPtrAssigner()
    {
        release<SP>(smart_ptr_, ptr_);
    }

private:
    // Instantiate exactly one of the two release() methods below, depending
    // on whether we were instantiated with a shared_ptr or a unique_ptr.

    template<typename T>
    typename std::enable_if<
        std::is_same<T, std::shared_ptr<typename T::element_type>>::value,
        void
    >::type
    release(std::shared_ptr<ElementType>& smart, ElementType* p)
    {
        smart.reset(p, GlibDeleter<typename T::element_type>());
    }

    template<typename T>
    typename std::enable_if<
        std::is_same<T, std::unique_ptr<typename T::element_type, typename T::deleter_type>>::value,
        void
    >::type
    release(std::unique_ptr<ElementType, GlibDeleter<typename T::element_type>>& smart, ElementType* p)
    {
        smart.reset(p);
    }

Pete, I've pushed this change here:

https://code.launchpad.net/~unity-api-team/unity-api/add-glib-assigner-class

It's now possible to use the same glib_assign method, whether the argument is a shared_ptr or a unique_ptr:

EXPECT_FALSE(g_key_file_get_boolean(gkf.get(), "group", "key", glib_assign(error)));

Revision history for this message
James Henstridge (jamesh) wrote :

I had a little play with your branch, and made a few cleanups in this diff:

http://paste.ubuntu.com/24232796/

Primarily:

1. move some of the detail types to the internal namespace
2. rename glib_assign_uptr and glib_assign_sptr to glib_assign()
3. merge GlibAssigner{UPtr,SPtr} implementations

There are some functions that are returning internal types, but they are either (a) types with public aliases, or (b) types that the caller shouldn't be referencing directly.

Revision history for this message
Pete Woods (pete-woods) wrote :

Argh! Too much help now! James and Michi's changes conflict!

(But seriously, thanks for everyone's help)

Revision history for this message
Michi Henning (michihenning) wrote :

Relax! Just look through both of them. Overall, James's approach is more elegant, I think. In particular, it doesn't need the SFINAE hackery.

Some of the comments I wrote would be nice to keep, I think.

One other thought: We should mark all methods that can't throw (I think that's like all of them) as noexcept.

And doing a pass for coding style would be good. (ptr_ instead _of _ptr, and the alignment of the member initialisers.)

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> > Function overloading?
>
> This works, but it means that each instance has two do_stuff() member
> functions, only one of which will ever be called, and the other one is dead
> code. (Admittedly, very little code.)

A class template only instantiates functions that are used.

Revision history for this message
Michi Henning (michihenning) wrote :

Aargh... I live and learn. Thanks for that!

Revision history for this message
Pete Woods (pete-woods) wrote :

:) I'm only being dramatic.

I've pulled michi's branch for the style changes, and I'm going to weave James' implementation of the destructor in, as it's definitely much simpler!

Revision history for this message
Pete Woods (pete-woods) wrote :

I'm not totally convinced by making all the types internal approach. But other than that this is looking very good.

Revision history for this message
Pete Woods (pete-woods) wrote :

For formatting style I'm just using Eclipse's BSD/Allman mode, FYI.

Revision history for this message
Pete Woods (pete-woods) wrote :

Right, this should be in a state for a final review now, unless we want to argue about the internal namespace thing.

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:287
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/161/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/4628
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/4656
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4483
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4483/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4483
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4483/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4483
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4483/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4483
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4483/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4483
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4483/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4483
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4483/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/161/rebuild

review: Approve (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:288
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/162/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/4629
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/4657
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4484
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/4484/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4484
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/4484/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4484
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/4484/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4484
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/4484/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4484
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/4484/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4484
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/4484/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity-api-ci/162/rebuild

review: Approve (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

This looks good to me. Thanks for pushing this through, Pete!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/changelog'
--- debian/changelog 2017-03-17 11:08:20 +0000
+++ debian/changelog 2017-04-04 09:33:34 +0000
@@ -1,3 +1,9 @@
1unity-api (8.7-0ubuntu1) UNRELEASED; urgency=medium
2
3 * Add Glib and GObject Assigner helpers.
4
5 -- Pete <pete.woods@canonical.com> Fri, 24 Mar 2017 10:25:45 +0000
6
1unity-api (8.6+17.04.20170317-0ubuntu1) zesty; urgency=medium7unity-api (8.6+17.04.20170317-0ubuntu1) zesty; urgency=medium
28
3 [ Michael Zanetti ]9 [ Michael Zanetti ]
410
=== modified file 'include/unity/util/GObjectMemory.h'
--- include/unity/util/GObjectMemory.h 2017-03-17 10:20:10 +0000
+++ include/unity/util/GObjectMemory.h 2017-04-04 09:33:34 +0000
@@ -57,7 +57,7 @@
57 */57 */
58struct GObjectDeleter58struct GObjectDeleter
59{59{
60 void operator()(gpointer ptr)60 void operator()(gpointer ptr) noexcept
61 {61 {
62 if (G_IS_OBJECT(ptr))62 if (G_IS_OBJECT(ptr))
63 {63 {
@@ -66,6 +66,51 @@
66 }66 }
67};67};
6868
69template<typename T> using GObjectSPtr = std::shared_ptr<T>;
70template<typename T> using GObjectUPtr = std::unique_ptr<T, GObjectDeleter>;
71
72namespace internal
73{
74
75template<typename SP>
76class GObjectAssigner
77{
78public:
79 typedef typename SP::element_type ElementType;
80
81 GObjectAssigner(SP& smart_ptr) noexcept:
82 smart_ptr_(smart_ptr)
83 {
84 }
85
86 GObjectAssigner(const GObjectAssigner& other) = delete;
87
88 GObjectAssigner(GObjectAssigner&& other) noexcept:
89 ptr_(other.ptr_), smart_ptr_(other.smart_ptr_)
90 {
91 other.ptr_ = nullptr;
92 }
93
94 ~GObjectAssigner() noexcept
95 {
96 smart_ptr_ = SP(ptr_, GObjectDeleter());
97 }
98
99 GObjectAssigner& operator=(const GObjectAssigner& other) = delete;
100
101 operator ElementType**() noexcept
102 {
103 return &ptr_;
104 }
105
106private:
107 ElementType* ptr_ = nullptr;
108
109 SP& smart_ptr_;
110};
111
112}
113
69/**114/**
70 \brief Helper method to wrap a unique_ptr around an existing GObject.115 \brief Helper method to wrap a unique_ptr around an existing GObject.
71116
@@ -79,11 +124,11 @@
79 \endcode124 \endcode
80 */125 */
81template<typename T>126template<typename T>
82inline std::unique_ptr<T, GObjectDeleter> unique_gobject(T* ptr)127inline GObjectUPtr<T> unique_gobject(T* ptr)
83{128{
84 check_floating_gobject(ptr);129 check_floating_gobject(ptr);
85 GObjectDeleter d;130 GObjectDeleter d;
86 return std::unique_ptr<T, GObjectDeleter>(ptr, d);131 return GObjectUPtr<T>(ptr, d);
87}132}
88133
89/**134/**
@@ -99,11 +144,11 @@
99 \endcode144 \endcode
100 */145 */
101template<typename T>146template<typename T>
102inline std::shared_ptr<T> share_gobject(T* ptr)147inline GObjectSPtr<T> share_gobject(T* ptr)
103{148{
104 check_floating_gobject(ptr);149 check_floating_gobject(ptr);
105 GObjectDeleter d;150 GObjectDeleter d;
106 return std::shared_ptr<T>(ptr, d);151 return GObjectSPtr<T>(ptr, d);
107}152}
108153
109/**154/**
@@ -117,7 +162,7 @@
117 \endcode162 \endcode
118 */163 */
119template<typename T, typename ... Args>164template<typename T, typename ... Args>
120inline std::unique_ptr<T, GObjectDeleter> make_gobject(GType object_type, const gchar *first_property_name, Args&&... args)165inline GObjectUPtr<T> make_gobject(GType object_type, const gchar *first_property_name, Args&&... args) noexcept
121{166{
122 gpointer ptr = g_object_new(object_type, first_property_name, std::forward<Args>(args)...);167 gpointer ptr = g_object_new(object_type, first_property_name, std::forward<Args>(args)...);
123 if (G_IS_OBJECT(ptr) && g_object_is_floating(ptr))168 if (G_IS_OBJECT(ptr) && g_object_is_floating(ptr))
@@ -127,6 +172,22 @@
127 return unique_gobject(G_TYPE_CHECK_INSTANCE_CAST(ptr, object_type, T));172 return unique_gobject(G_TYPE_CHECK_INSTANCE_CAST(ptr, object_type, T));
128}173}
129174
175/**
176 \brief Helper method to take ownership of GObjects assigned from a reference.
177
178 Example:
179 \code{.cpp}
180 GObjectUPtr<FooBar> o;
181 method_that_assigns_a_foobar(assign_gobject(o));
182 \endcode
183 */
184template<typename SP>
185inline internal::GObjectAssigner<SP> assign_gobject(SP& smart_ptr) noexcept
186{
187 return internal::GObjectAssigner<SP>(smart_ptr);
188}
189
190
130} // namespace until191} // namespace until
131192
132} // namespace unity193} // namespace unity
133194
=== modified file 'include/unity/util/GlibMemory.h'
--- include/unity/util/GlibMemory.h 2017-02-23 11:18:54 +0000
+++ include/unity/util/GlibMemory.h 2017-04-04 09:33:34 +0000
@@ -14,6 +14,8 @@
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Pete Woods <pete.woods@canonical.com>16 * Authored by: Pete Woods <pete.woods@canonical.com>
17 * Michi Henning <michi.henning@canonical.com>
18 * James Henstridge <james.henstridge@canonical.com>
17 */19 */
1820
19#ifndef UNITY_UTIL_GLIBMEMORY_H21#ifndef UNITY_UTIL_GLIBMEMORY_H
@@ -28,45 +30,161 @@
28namespace util30namespace util
29{31{
3032
31template<typename T, typename D>33namespace internal
32struct GlibDeleter34{
33{35
34 D _d;36template<typename T> struct GlibDeleter;
3537template<typename T> using GlibSPtr = std::shared_ptr<T>;
36 void operator()(T* ptr)38template<typename T> using GlibUPtr = std::unique_ptr<T, GlibDeleter<T>>;
37 {39
38 if (ptr != nullptr)40/**
39 {41 * \brief Adapter class to assign to smart pointers from functions that take a reference.
40 _d(ptr);42 *
41 }43 * Adapter class that allows passing a shared_ptr or unique_ptr where glib
42 }44 * expects a parameter of type ElementType** (such as GError**), by providing
45 * a default conversion operator to ElementType**. This allows the glib method
46 * to assign to the ptr_ member. From the destructor, we assign to the
47 * provided smart pointer.
48 */
49template<typename SP>
50class GlibAssigner
51{
52public:
53 typedef typename SP::element_type ElementType;
54
55 GlibAssigner(SP& smart_ptr) noexcept :
56 smart_ptr_(smart_ptr)
57 {
58 }
59
60 GlibAssigner(const GlibAssigner& other) = delete;
61
62 GlibAssigner(GlibAssigner&& other) noexcept:
63 ptr_(other.ptr_), smart_ptr_(other.smart_ptr_)
64 {
65 other.ptr_ = nullptr;
66 }
67
68 ~GlibAssigner() noexcept
69 {
70 smart_ptr_ = SP(ptr_, GlibDeleter<ElementType>());
71 }
72
73 GlibAssigner& operator=(const GlibAssigner& other) = delete;
74
75 operator ElementType**() noexcept
76 {
77 return &ptr_;
78 }
79
80private:
81 ElementType* ptr_ = nullptr;
82
83 SP& smart_ptr_;
43};84};
4485
86}
87
88#define UNITY_UTIL_DEFINE_GLIB_SMART_POINTERS(TypeName, func) \
89using TypeName##Deleter = internal::GlibDeleter<TypeName>; \
90using TypeName##SPtr = internal::GlibSPtr<TypeName>; \
91using TypeName##UPtr = internal::GlibUPtr<TypeName>; \
92namespace internal \
93{ \
94template<> struct GlibDeleter<TypeName> \
95{ \
96 void operator()(TypeName* ptr) noexcept \
97 { \
98 if (ptr) \
99 { \
100 ::func(ptr); \
101 } \
102 } \
103}; \
104}
105
106/**
107 \brief Helper method to wrap a shared_ptr around a Glib type.
108
109 Example:
110 \code{.cpp}
111 auto gkf = shared_glib(g_key_file_new());
112 \endcode
113 */
114template<typename T>
115inline internal::GlibSPtr<T> share_glib(T* ptr) noexcept
116{
117 return internal::GlibSPtr<T>(ptr, internal::GlibDeleter<T>());
118}
119
120/**
121 \brief Helper method to wrap a unique_ptr around a Glib type.
122
123 Example:
124 \code{.cpp}
125 auto gkf = unique_glib(g_key_file_new());
126 \endcode
127 */
128template<typename T>
129inline internal::GlibUPtr<T> unique_glib(T* ptr) noexcept
130{
131 return internal::GlibUPtr<T>(ptr, internal::GlibDeleter<T>());
132}
133
134/**
135 \brief Helper method to take ownership of glib types assigned from a reference.
136
137 Example:
138 \code{.cpp}
139 GErrorUPtr error;
140 if (!g_key_file_get_boolean(gkf.get(), "group", "key", assign_glib(error)))
141 {
142 std::cerr << error->message << std::endl;
143 throw some_exception();
144 }
145 \endcode
146
147 Another example:
148 \code{.cpp}
149 gcharUPtr name;
150 g_object_get(obj, "name", assign_glib(name), nullptr);
151 \endcode
152 */
153template<typename SP>
154inline internal::GlibAssigner<SP> assign_glib(SP& smart_ptr) noexcept
155{
156 return internal::GlibAssigner<SP>(smart_ptr);
157}
158
159/**
160 * Below here is some hackery to extract the matching deleters for all built in glib types.
161 */
45#pragma push_macro("G_DEFINE_AUTOPTR_CLEANUP_FUNC")162#pragma push_macro("G_DEFINE_AUTOPTR_CLEANUP_FUNC")
46#undef G_DEFINE_AUTOPTR_CLEANUP_FUNC163#undef G_DEFINE_AUTOPTR_CLEANUP_FUNC
47#define G_DEFINE_AUTOPTR_CLEANUP_FUNC(TypeName, func) \164#define G_DEFINE_AUTOPTR_CLEANUP_FUNC(TypeName, func) UNITY_UTIL_DEFINE_GLIB_SMART_POINTERS(TypeName, func)
48typedef GlibDeleter<TypeName, decltype(&func)> TypeName##Deleter; \
49typedef std::shared_ptr<TypeName> TypeName##SPtr; \
50inline TypeName##SPtr share_glib(TypeName* ptr) \
51{ \
52 return TypeName##SPtr(ptr, TypeName##Deleter{&func}); \
53} \
54typedef std::unique_ptr<TypeName, TypeName##Deleter> TypeName##UPtr; \
55inline TypeName##UPtr unique_glib(TypeName* ptr) \
56{ \
57 return TypeName##UPtr(ptr, TypeName##Deleter{&func}); \
58}
59165
60#pragma push_macro("G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC")166#pragma push_macro("G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC")
61#undef G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC167#undef G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC
62#define G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(TypeName, func)168#define G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(TypeName, func)
63169
170#pragma push_macro("G_DEFINE_AUTO_CLEANUP_FREE_FUNC")
171#undef G_DEFINE_AUTO_CLEANUP_FREE_FUNC
172#define G_DEFINE_AUTO_CLEANUP_FREE_FUNC(TypeName, func, none)
173
64#define __GLIB_H_INSIDE__174#define __GLIB_H_INSIDE__
65#include <glib/glib-autocleanups.h>175#include <glib/glib-autocleanups.h>
66#undef __GLIB_H_INSIDE__176#undef __GLIB_H_INSIDE__
67177
68#pragma pop_macro("G_DEFINE_AUTOPTR_CLEANUP_FUNC")178#pragma pop_macro("G_DEFINE_AUTOPTR_CLEANUP_FUNC")
69#pragma pop_macro("G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC")179#pragma pop_macro("G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC")
180#pragma pop_macro("G_DEFINE_AUTO_CLEANUP_FREE_FUNC")
181
182/**
183 * Manually add extra definitions for gchar* and gchar**
184 */
185UNITY_UTIL_DEFINE_GLIB_SMART_POINTERS(gchar, g_free)
186typedef gchar* gcharv;
187UNITY_UTIL_DEFINE_GLIB_SMART_POINTERS(gcharv, g_strfreev)
70188
71} // namespace until189} // namespace until
72190
73191
=== modified file 'test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp'
--- test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp 2017-03-17 10:20:10 +0000
+++ test/gtest/unity/util/GObjectMemory/GObjectMemory_test.cpp 2017-04-04 09:33:34 +0000
@@ -17,8 +17,8 @@
17 * Pete Woods <pete.woods@canonical.com>17 * Pete Woods <pete.woods@canonical.com>
18 */18 */
1919
20#include <unity/util/GlibMemory.h>
20#include <unity/util/GObjectMemory.h>21#include <unity/util/GObjectMemory.h>
21#include <glib-object.h>
22#include <gtest/gtest.h>22#include <gtest/gtest.h>
23#include <list>23#include <list>
24#include <string>24#include <string>
@@ -46,8 +46,14 @@
4646
47FooBar *foo_bar_new();47FooBar *foo_bar_new();
4848
49void foo_bar_assigner(FooBar** in);
50
49FooBar *foo_bar_new_full(const gchar* const name, guint id);51FooBar *foo_bar_new_full(const gchar* const name, guint id);
5052
53void foo_bar_assigner_full(const gchar* const name, guint id, FooBar** in);
54
55void foo_bar_assigner_null(FooBar** in);
56
51G_END_DECLS57G_END_DECLS
5258
53// private implementation59// private implementation
@@ -167,6 +173,30 @@
167 return FOO_BAR(g_object_new(FOO_TYPE_BAR, "name", name, "id", id, NULL));173 return FOO_BAR(g_object_new(FOO_TYPE_BAR, "name", name, "id", id, NULL));
168}174}
169175
176void foo_bar_assigner(FooBar** in)
177{
178 if (in != nullptr)
179 {
180 *in = foo_bar_new();
181 }
182}
183
184void foo_bar_assigner_full(const gchar* const name, guint id, FooBar** in)
185{
186 if (in != nullptr)
187 {
188 *in = foo_bar_new_full(name, id);
189 }
190}
191
192void foo_bar_assigner_null(FooBar** in)
193{
194 if (in != nullptr)
195 {
196 *in = nullptr;
197 }
198}
199
170//200//
171// Test cases201// Test cases
172//202//
@@ -337,12 +367,12 @@
337}367}
338368
339TEST_F(GObjectMemoryTest, sizeoftest) {369TEST_F(GObjectMemoryTest, sizeoftest) {
340 EXPECT_EQ(sizeof(FooBar*), sizeof(unique_ptr<FooBar>));370 EXPECT_EQ(sizeof(FooBar*), sizeof(GObjectUPtr<FooBar>));
341}371}
342372
343TEST_F(GObjectMemoryTest, hash)373TEST_F(GObjectMemoryTest, hash)
344{374{
345 unordered_set<shared_ptr<FooBar>> s;375 unordered_set<GObjectSPtr<FooBar>> s;
346 auto a = share_gobject(foo_bar_new());376 auto a = share_gobject(foo_bar_new());
347 auto b = share_gobject(foo_bar_new());377 auto b = share_gobject(foo_bar_new());
348 auto c = share_gobject(foo_bar_new());378 auto c = share_gobject(foo_bar_new());
@@ -403,10 +433,57 @@
403 EXPECT_EQ(list<Deleted>({{"c", 3}, {"b", 2}, {"a", 1}}), DELETED_OBJECTS);433 EXPECT_EQ(list<Deleted>({{"c", 3}, {"b", 2}, {"a", 1}}), DELETED_OBJECTS);
404}434}
405435
406TEST_F(GObjectMemoryTest, foo)436TEST_F(GObjectMemoryTest, uptrAssignerDeletesGObjects)
407{437{
408 {438 {
409 shared_ptr<FooBar> s;439 GObjectUPtr<FooBar> a, b;
440 foo_bar_assigner_full("a", 1, assign_gobject(a));
441 foo_bar_assigner_full("b", 2, assign_gobject(b));
442 }
443 EXPECT_EQ(list<Deleted>({{"b", 2}, {"a", 1}}), DELETED_OBJECTS);
444}
445
446TEST_F(GObjectMemoryTest, sptrAssignerDeletesGObjects)
447{
448 {
449 GObjectSPtr<FooBar> a, b, c;
450 foo_bar_assigner_full("a", 3, assign_gobject(a));
451 foo_bar_assigner_full("b", 4, assign_gobject(b));
452 foo_bar_assigner_full("c", 5, assign_gobject(c));
453 }
454 EXPECT_EQ(list<Deleted>({{"c", 5}, {"b", 4}, {"a", 3}}), DELETED_OBJECTS);
455}
456
457TEST_F(GObjectMemoryTest, uptrAssignerAssignsNull)
458{
459 {
460 GObjectUPtr<FooBar> o;
461 foo_bar_assigner_full("o", 1, assign_gobject(o));
462 ASSERT_TRUE(bool(o));
463 foo_bar_assigner_null(assign_gobject(o));
464 ASSERT_FALSE(bool(o));
465
466 }
467 EXPECT_EQ(list<Deleted>({{"o", 1}}), DELETED_OBJECTS);
468}
469
470TEST_F(GObjectMemoryTest, sptrAssignerAssignsNull)
471{
472 {
473 GObjectSPtr<FooBar> o;
474 foo_bar_assigner_full("o", 1, assign_gobject(o));
475 ASSERT_TRUE(bool(o));
476 foo_bar_assigner_null(assign_gobject(o));
477 ASSERT_FALSE(bool(o));
478
479 }
480 EXPECT_EQ(list<Deleted>({{"o", 1}}), DELETED_OBJECTS);
481}
482
483TEST_F(GObjectMemoryTest, moveUptrSptr)
484{
485 {
486 GObjectSPtr<FooBar> s;
410 auto u = unique_gobject(foo_bar_new_full("hi", 6));487 auto u = unique_gobject(foo_bar_new_full("hi", 6));
411 s = move(u);488 s = move(u);
412 // unique instance has been stolen from489 // unique instance has been stolen from
@@ -414,7 +491,7 @@
414 }491 }
415 EXPECT_EQ(list<Deleted>({{"hi", 6}}), DELETED_OBJECTS);492 EXPECT_EQ(list<Deleted>({{"hi", 6}}), DELETED_OBJECTS);
416 {493 {
417 shared_ptr<FooBar> s(unique_gobject(foo_bar_new_full("bye", 7)));494 GObjectSPtr<FooBar> s(unique_gobject(foo_bar_new_full("bye", 7)));
418 }495 }
419 EXPECT_EQ(list<Deleted>({{"hi", 6}, {"bye", 7}}), DELETED_OBJECTS);496 EXPECT_EQ(list<Deleted>({{"hi", 6}, {"bye", 7}}), DELETED_OBJECTS);
420}497}
@@ -431,14 +508,12 @@
431 */508 */
432 static void checkProperties(gpointer obj, const char* expectedName, guint expectedId)509 static void checkProperties(gpointer obj, const char* expectedName, guint expectedId)
433 {510 {
434 char* name = NULL;511 gcharUPtr name;
435 guint id = 0;512 guint id = 0;
436513
437 g_object_get(obj, "name", &name, "id", &id, NULL);514 g_object_get(obj, "name", assign_glib(name), "id", &id, nullptr);
438 EXPECT_STREQ(expectedName, name);515 EXPECT_STREQ(expectedName, name.get());
439 EXPECT_EQ(expectedId, id);516 EXPECT_EQ(expectedId, id);
440
441 g_free(name);
442 }517 }
443};518};
444519
@@ -471,6 +546,22 @@
471 checkProperties(obj.get(), p.first, p.second);546 checkProperties(obj.get(), p.first, p.second);
472}547}
473548
549TEST_P(GObjectMemoryMakeHelperMethodsTest, assign_foo_bar_uptr_assigner)
550{
551 auto p = GetParam();
552 GObjectUPtr<FooBar> obj;
553 foo_bar_assigner_full(p.first, p.second, assign_gobject(obj));
554 checkProperties(obj.get(), p.first, p.second);
555}
556
557TEST_P(GObjectMemoryMakeHelperMethodsTest, assign_foo_bar_sptr_assigner)
558{
559 auto p = GetParam();
560 GObjectSPtr<FooBar> obj;
561 foo_bar_assigner_full(p.first, p.second, assign_gobject(obj));
562 checkProperties(obj.get(), p.first, p.second);
563}
564
474INSTANTIATE_TEST_CASE_P(BunchOfNames,565INSTANTIATE_TEST_CASE_P(BunchOfNames,
475 GObjectMemoryMakeHelperMethodsTest,566 GObjectMemoryMakeHelperMethodsTest,
476 ::testing::Values(GObjectMemoryMakeSharedTestParam{"meeny", 1},567 ::testing::Values(GObjectMemoryMakeSharedTestParam{"meeny", 1},
477568
=== modified file 'test/gtest/unity/util/GlibMemory/GlibMemory_test.cpp'
--- test/gtest/unity/util/GlibMemory/GlibMemory_test.cpp 2017-02-23 11:18:54 +0000
+++ test/gtest/unity/util/GlibMemory/GlibMemory_test.cpp 2017-04-04 09:33:34 +0000
@@ -89,6 +89,30 @@
89 {89 {
90 return share_glib(g_hash_table_new_full(g_str_hash, g_str_equal, deleteKey, deleteValue));90 return share_glib(g_hash_table_new_full(g_str_hash, g_str_equal, deleteKey, deleteValue));
91 }91 }
92
93 static void assignGChar(gchar** in)
94 {
95 if (in != nullptr)
96 {
97 *in = g_strdup("hi");
98 }
99 }
100
101 static void assignGError(GError** in)
102 {
103 if (in != nullptr)
104 {
105 *in = g_error_new_literal(g_key_file_error_quark(), 1, "hello");
106 }
107 }
108
109 static void assignEmptyGError(GError** in)
110 {
111 if (in != nullptr)
112 {
113 *in = nullptr;
114 }
115 }
92};116};
93117
94TEST_F(GlibMemoryTest, Unique)118TEST_F(GlibMemoryTest, Unique)
@@ -147,4 +171,108 @@
147 }171 }
148}172}
149173
174TEST_F(GlibMemoryTest, UPtrAssigner)
175{
176 auto gkf = unique_glib(g_key_file_new());
177
178 {
179 GErrorUPtr error;
180 EXPECT_FALSE(g_key_file_get_boolean(gkf.get(), "group", "key", assign_glib(error)));
181 ASSERT_TRUE(bool(error));
182 EXPECT_EQ(G_KEY_FILE_ERROR_GROUP_NOT_FOUND, error->code);
183 }
184
185 g_key_file_set_boolean(gkf.get(), "group", "key", TRUE);
186
187 {
188 GErrorUPtr error;
189 EXPECT_TRUE(g_key_file_get_boolean(gkf.get(), "group", "key", assign_glib(error)));
190 EXPECT_FALSE(bool(error));
191 }
192
193 {
194 gcharUPtr str;
195 assignGChar(assign_glib(str));
196 ASSERT_TRUE(bool(str));
197 EXPECT_STREQ("hi", str.get());
198 }
199
200 {
201 GErrorUPtr error;
202 assignGError(assign_glib(error));
203 ASSERT_TRUE(bool(error));
204 assignEmptyGError(assign_glib(error));
205 ASSERT_FALSE(bool(error));
206 }
207}
208
209TEST_F(GlibMemoryTest, SPtrAssigner)
210{
211 auto gkf = share_glib(g_key_file_new());
212
213 {
214 GErrorSPtr error;
215 EXPECT_FALSE(g_key_file_get_boolean(gkf.get(), "group", "key", assign_glib(error)));
216 ASSERT_TRUE(bool(error));
217 EXPECT_EQ(G_KEY_FILE_ERROR_GROUP_NOT_FOUND, error->code);
218 }
219
220 g_key_file_set_boolean(gkf.get(), "group", "key", TRUE);
221
222 {
223 GErrorSPtr error;
224 EXPECT_TRUE(g_key_file_get_boolean(gkf.get(), "group", "key", assign_glib(error)));
225 EXPECT_FALSE(bool(error));
226 }
227
228 {
229 gcharSPtr str;
230 assignGChar(assign_glib(str));
231 ASSERT_TRUE(bool(str));
232 EXPECT_STREQ("hi", str.get());
233 }
234
235 {
236 GErrorSPtr error;
237 assignGError(assign_glib(error));
238 ASSERT_TRUE(bool(error));
239 assignEmptyGError(assign_glib(error));
240 ASSERT_FALSE(bool(error));
241 }
242}
243
244TEST_F(GlibMemoryTest, GCharUPtr)
245{
246 {
247 auto strv = unique_glib(g_strsplit("a b c", " ", 0));
248 ASSERT_TRUE(bool(strv));
249 EXPECT_STREQ("a", strv.get()[0]);
250 EXPECT_STREQ("b", strv.get()[1]);
251 EXPECT_STREQ("c", strv.get()[2]);
252 }
253
254 {
255 auto str = unique_glib(g_strdup("hello"));
256 ASSERT_TRUE(bool(str));
257 EXPECT_STREQ("hello", str.get());
258 }
259}
260
261TEST_F(GlibMemoryTest, GCharSPtr)
262{
263 {
264 auto strv = share_glib(g_strsplit("a b c", " ", 0));
265 ASSERT_TRUE(bool(strv));
266 EXPECT_STREQ("a", strv.get()[0]);
267 EXPECT_STREQ("b", strv.get()[1]);
268 EXPECT_STREQ("c", strv.get()[2]);
269 }
270
271 {
272 auto str = share_glib(g_strdup("hello"));
273 ASSERT_TRUE(bool(str));
274 EXPECT_STREQ("hello", str.get());
275 }
276}
277
150}278}

Subscribers

People subscribed via source and target branches

to all changes: