User: Password:
|
|
Subscribe / Log in / New account

Clasen: Introducing GtkInspector

Clasen: Introducing GtkInspector

Posted May 18, 2014 18:41 UTC (Sun) by djcb (guest, #41542)
In reply to: Clasen: Introducing GtkInspector by rleigh
Parent article: Clasen: Introducing GtkInspector

I use both C and C++.

For the typical API-user, I've found the GLib/Gobject-based libraries of high quality (think GLib itself, GStreamer, json-glib, soup, GDBus), esp. with respect to the ease of using some of the G-niceties such as signals, watching properties for changes (or even binding them).

A hurdle comes when writing your own GObjects; that indeed requires a bit of practice - but it's not /so/ hard either -- not harder than some of the magic in 'modern' C++. There are some niceties in C++ that I miss in C sometimes -- such as some simplified memory management, and the ability to create small helper classes; perhaps lambdas. But overall, I like the clarity, simplicity of C, and I find that it's the most natural way to use the aforementioned libraries. Don't call me a 'zealot' for that!

Anyway, when we go beyond C, I hope one of the next-gen languages becomes a feasible choice (such a D or rust).


(Log in to post comments)

Clasen: Introducing GtkInspector

Posted May 18, 2014 21:17 UTC (Sun) by rleigh (guest, #14622) [Link]

I don't disagree with your point that these are high quality libraries. But I would qualify that with "high quality for C". When you compare their quality with C++ equivalents they are sorely lacking. Taking signals for example, the GObject-based signals are quite crude and type-unsafe. If you compare them with libsigc++ or Boost.Signals they aren't even on the same playing field. Likewise for Qt signals, though these are not quite as good due to doing method signature-checking at runtime rather than at compile-time with templates as the others do. And these libraries also allow adapters to add/remove/reorder the signal parameters and return type, so are also much more flexible as well as much more robust. Stuff like g_closure_invoke/g_cclosure_* and all the generated C marshallers are the stuff of nightmares in comparison. Today in C++ you can do stuff like using lambdas for signal handlers; it's a totally different level. (You can use libsigc++ on top of GObject with GLibmm of course, but it's still built on top of the gobject house of cards if you do that.)

Likewise, while for some writing your own GObjects might not be considered "hard", I have to disagree--it's non-obvious and fragile, and isn't properly documented. I wrote a tutorial after figuring it out which was, at the time, only the second source of information on how to do it since it was completely undocumented (http://www.codelibre.net/~rleigh/gtk/ogcalc.pdf). But the "hardness" isn't the only issue. For most people, it's robustness and maintainability. It's also not just about whether *you* can do it, it's whether all the other people on the development team get it as well, plus any future maintainers and potential collaborators. If you can do it, great, but we need to realise that this requires skills beyond what most C and even C++ programmers have--these are not learned language features, but esoteric special stuff which isn't obvious until you invest significant time to figure it all out. To get to that point you need to learn and understand C++, and this does lead to questioning why we aren't just using C++ since by this point the user is perfectly capable of using C++, while they still need to learn the arcane bits of GObject on top of that to start using it, and it will only ever be inferior to doing the same thing in C++. Most potential contributors will find the barrier to entry much lower if using C++ since it's so much more simple and obvious.

class MyWidget : public Widget { … };

is easy; it's C++ 101, right out of the book. But doing the same with GObject is complex and fragile--several pages of boilerplate. Any typos or mistakes and it's broken, leading to crashes or misbehaviour. And keeping the vtables and all the macros up to date with code changes is a manual process; with C++ it's all done and checked for you by the compiler. Think about how much work is needed to refactor the inheritance hierarchy. It's maybe a 1-2 line change in C++, but with GObject you have to change it in numerous places, with nothing to catch any mistakes. In no way is this robust or maintainable! For most teams, doing this is simply not tenable; C++ does all this with compile-time checking, so giving it up for a more complex and fragile solution makes no sense.

By zealot, I really meant it as referring to people who are so single-minded and blinkered about C that they are not prepared to acknowledge that it has any deficiencies or limitations, and that there are situations where other languages serve better. That's been an attitude prevalent in the Linux community since forever, and I don't think it's always healthy (and I admit I was one of these people up until around 2005). GTK+ was kind-of understandable back in 1998 when we were stuck with GCC 2.95 which was still poor for C++ work pre-Standard; since GCC 3.x this hasn't really been true, and today it (and clang++) are absolutely top-notch. If you get to the point in C where you start to think, "hmm, maybe I should use GObject", IMO that's the point where you should be thinking about using C++ instead because that's the point where you're moving outside C's capabilities and sticking with C will lead to a much less robust and maintainable result than C++ (or even Python).

Clasen: Introducing GtkInspector

Posted May 19, 2014 2:29 UTC (Mon) by djcb (guest, #41542) [Link]

Thanks for your insights.

Sure, learning any tool requires a bit of practice (with the exception
of the nipple, I suppose), this is true when writing your own GObjects
just like using, say, std::bind. But nothing that spectacularly
difficult, I think.

I like the C/GObject idiom for the concise and readable client-code it
allows, but I'll happily write C++ when that's a better fit for
the problem I try to solve (even if there's a bit more 'magic'). I
understand personal preferences differ, but you really don't have to
be some irrational reactionary to enjoy writing C code...

I understand that people want to use languages that are a bit more
expressive, but C++ doesn't really fit the bill, I think... looking
forward to some language that can replace both.

Clasen: Introducing GtkInspector

Posted May 20, 2014 0:29 UTC (Tue) by Trelane (subscriber, #56877) [Link]

So you like gtkmm, i take it?

Clasen: Introducing GtkInspector

Posted May 20, 2014 19:56 UTC (Tue) by rleigh (guest, #14622) [Link]

I did like GTKmm. It's clean, well written and documented, and a pleasure to use. It's really what GTK should have been; had this API been the base rather than C, it would have been properly type-safe and robust. And the libsigc++ signals (were) nicer and safer than Qt signals. Probably the nicest C++ language binding I've used.

That said, I ran into minor issues with unwrapped new functionality, and the very occasional bug. Not blockers, just temporary annoyances. But underlying bugs which are still unfixed were rather more serious, like Glade UIs losing all their accelerators when you reparent them into a new window (https://bugzilla.gnome.org/show_bug.cgi?id=129846). Quite a common thing to do if you want all your main UIs as embeddable custom widgets constructed from data rather than code. May be fixed by GTKbuilder, but I haven't tried that; it was still missing some required functionality last time I looked so I stuck with libglademm. These unfixed fairly fundamental things ultimately led to GTK being dropped. Today, there isn't even an upgrade path from .glade to .builder; it was stripped out of glade so you can't even open them. The code will remain using GTK2 GTKmm/libglademm indefinitely; it's not currently worth the expense of a port to GTK3 (it provides zero benefit); it would be less effort to convert from GTKmm directly to Qt should moving from GTK2 be required.

Clasen: Introducing GtkInspector

Posted May 18, 2014 21:27 UTC (Sun) by HelloWorld (guest, #56129) [Link]

> I like the clarity, simplicity of C
C is neither simple nor clear. How many people understand that you need to explicitly cast a pointer to void* when printing it with printf and %p? Who can keep all those silly implicit conversions in their head? How many know what the type of 'a' is? How many C programmers *actually* understand C declaration syntax? Or the silly operator precedences? How many understand what undefined behaviour actually means?

And let's not forget about the gratuitous complexity in user code that is ultimately caused by C's lack of basic features, such as generics or strings, or by its useless and confusing conflation of arrays and pointers, or its lack of orthogonality (it's been known at least since Algol that the distinction between statements and expressions is useless and harmful), or by antifeatures like the preprocessor.

So please, stop spreading this “clarity” and “simplicity” nonsense. By now, we must call C what it is: a really lame, 70ies-era technology that should never have been used outside the kernel in the first place. It should have been shot in the head 25 years ago, and not lauded as an example for clarity and simplicity in 2014.

Clasen: Introducing GtkInspector

Posted May 18, 2014 22:14 UTC (Sun) by brouhaha (subscriber, #1698) [Link]

C combines the power and flexibility of assembly language with the ease of use, reliability, and maintainability of assembly language.

C was a mostly reasonable language for systems programming on a PDP-11 in the 1970s. It's not a reasonable language for much of anything today.

That said, most of my income is from writing C code. :-(

C vs C++

Posted May 19, 2014 8:30 UTC (Mon) by rvfh (subscriber, #31018) [Link]

Ditto. I must use C at work (and then find memory leaks, understand poorly written code, deal with lack of architecture), but I use only C++ at home.

C++ (as most OO languages I suppose) allows a better representation of the problem, and I find that nice C code usually looks like:

blah_init()
void* handle = blah_create()
int rc = blah_do_something(handle, some data)
blah_destroy(handle)

IOW, people have tried to write OO code with a non-OO language. The Linux kernel is full of this kind of things, not even mentioning structures containing function pointers.

So if what people want is to write OO code, I find it easier to do it with a OO language!

C vs C++

Posted May 19, 2014 9:53 UTC (Mon) by ehiggs (subscriber, #90713) [Link]

I'm not sure that everyone agrees that using opaque pointers constitutes an attempt to write OO code in C. There's no polymorphism, or inheritance, or other features more commonly known as making up OO. I think you've just given an example of modular programming and I think we can all agree that this is a good approach for a lot of code.

As far as C++ providing a better representation of the problem, it certainly can. However, in your example writing C code in the manner you've shown has less boiler plate than trying to use the pimpl pattern in C++. In the pimpl pattern class definitions are basically doubled, functions have to jump through a proxy.

C vs C++

Posted May 19, 2014 16:27 UTC (Mon) by zlynx (subscriber, #2285) [Link]

Not necessarily. If you are happy having your functions reference data through the pImpl pointer you just have one set of functions and the private data is in the impl struct. You do always need duplicate constructors though, which is annoying.

C vs C++

Posted May 19, 2014 20:28 UTC (Mon) by MrWim (subscriber, #47432) [Link]

I usually prefer to use abstract base classes for data hiding. There is a runtime cost but that is rarely significant. It cuts down on the duplication you usually incur with pImpl and you often want an abstract base class anyway to enable mocking.

Obviously this doesn't apply to value types, but nor does pImpl usually either.

C vs C++

Posted May 20, 2014 7:59 UTC (Tue) by rvfh (subscriber, #31018) [Link]

How do abstract classes solve the issue of 'I want all my private stuff in the cpp file', hidden from view?

C vs C++

Posted May 20, 2014 11:14 UTC (Tue) by MrWim (subscriber, #47432) [Link]

Like this:

Foo.h:
class Foo : boost::noncopyable
{
public:
    virtual ~Foo();
    virtual void frob() = 0;
    virtual void frell() = 0;
};

std::shared_ptr<Foo> makeFoo(int some, const std::string& args);

Foo.cpp:

#include "Foo.h"

// Force RTTI information out here so it's not spammed all over the place
Foo::~Foo()
{
}

namespace { // Anon namespace, all symbols confined to this translation unit

struct ConcreteFoo : public Foo
{
    ConcreteFoo() {}
    virtual void frob() {
        printf("frob!\n");
    };
    virtual void frell() {
        printf("frell!\n");
    }
};

std::shared_ptr<Foo> makeFoo(int some, const std::string& args)
{
    return std::make_shared<ConcreteFoo>();
}

It's nice because there is the same number of repetitions of method names as if you were just following the usual pattern of declaration in header file, implementation in cpp file. Also, because it's not a value type, you are likely to be holding it by pointer anyway. If Foo were a pImpl instead you would be dealing with a pointer to a pImpl class which is essentially a pointer anyway.

You can also do things like force management by shared_ptr by changing the factory function. You don't need to faf around with making make_shared a friend or having friend factory functions or anything like that.

Another nice aspect is that you don't need to use private: anywhere. The fact that all the code is in the cpp file offers stronger encapsulation than private:.

The one down-side I'm aware of is that it makes it a little tricker to write white-box tests of the implementation details of the class. The solution I've used in the past is to just #include "Foo.cpp" into my tests. Mostly I prefer black-box testing anyway though so it's not a big deal.

C vs C++

Posted May 20, 2014 14:26 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

How do you deal with friend classes in this case? "private: friend ...;"? My example is a "pool" being able to have extra access to the objects in it basically to tell the objects information only it could know.

My problem with it is that now you force shared_ptr on it. What if I want a unique_ptr?

C vs C++

Posted May 20, 2014 18:24 UTC (Tue) by MrWim (subscriber, #47432) [Link]

How do you deal with friend classes in this case? "private: friend ...;"? My example is a "pool" being able to have extra access to the objects in it basically to tell the objects information only it could know.
I'm not sure I understand what you're describing, but one way would be to define the pool and the objects in the same cpp file where they can be aware of each other. No friends required ;). Ultimately this technique isn't a panacea and you can apply it where it works and don't where it doesn't. It just seems to me that it's superior to pImpl for most use-cases but seems to be under-used as a technique (unlike pImpl).
My problem with it is that now you force shared_ptr on it. What if I want a unique_ptr?
That was just an example of something that is convenient to do with this approach. Of course if you want a unique_ptr then return a unique_ptr.

C vs C++

Posted May 20, 2014 18:30 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

> I'm not sure I understand what you're describing, but one way would be to define the pool and the objects in the same cpp file where they can be aware of each other.

These are the two largest source files in the repo already :) . There are actually a few things in that class[1] that break pImpl cleanliness anyways: templates which call a private method.

Basically, only the 'pipeline' class knows what the "core frequency" is, so it is locked down to be private and not exported from the library to protect it.

[1]https://github.com/Kitware/sprokit/blob/master/src/sproki...

C vs C++

Posted May 20, 2014 16:53 UTC (Tue) by ehiggs (subscriber, #90713) [Link]

Thanks for sharing this. It's not too bad but I am surprised we would want to have makeFoo return a shared pointer. Why not make it return a raw pointer so the user can determine how to package it? Is this a common practice you see in C++ was just this part of a throwaway example?

Thanks

C vs C++

Posted May 20, 2014 17:49 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

Depends on how opinionated the library is. Ownership tracking of pointers is still a pain in C++ (which Rust finally addresses). Forcing shared_ptr avoids ownership problems at the cost of a reference count (not the worst thing). I just think that unique_ptr should also be an option here at least.

C vs C++

Posted May 20, 2014 17:55 UTC (Tue) by MrWim (subscriber, #47432) [Link]

It's just an example of something that is hard(er) to do otherwise as it can require faffing about with friend classes/functions. As you say, when there's no reason to return a shared_ptr don't. A unique_ptr or an auto_ptr would be just as good in this example, or if you have an aversion to them a raw pointer would also work.

You could even expose a size variable and a placement new factory function for ultimate flexibility, allowing even stack allocation of these objects, but this would almost certainly be over the top.

C vs C++

Posted May 20, 2014 19:22 UTC (Tue) by JGR (subscriber, #93631) [Link]

auto_ptr has nasty and unhelpful copy behaviour which led to it being deprecated, it should be avoided if at all possible IMO.

unique_ptr is generally the most convenient choice; constructing one has zero cost over just returning a pointer to an allocation with new. It's trivial to then move it to a different smart pointer/whatever as necessary, which is much more of a pain with a shared_pointer. Destructing a shared_ptr also uses atomic operations, which is surprisingly expensive.

If you need that level of allocation flexibility and always return a fixed number of (non-polymorphic) object(s), it's usually simpler to just require that the caller pass in a non-const reference to an existing object to fill in.
For the case of 1 object, you could also use a member function or constructor.

C vs C++

Posted May 21, 2014 14:58 UTC (Wed) by mathstuf (subscriber, #69389) [Link]

Well, the problem with unique_ptr is that you need move constructors for it to be fully useful. And that requires C++11 or using boost::move explicitly. Unfortunately, the list of platforms I need to support limit me to shared_ptr since decorating everything with boost::move would likely cause more bugs when it is forgotten.

C vs C++

Posted May 21, 2014 21:07 UTC (Wed) by jwakely (guest, #60262) [Link]

Returning a unique_ptr is better than a raw pointer - it's safe-by-default and also explicit that there is ownership transfer going on. Returning a raw pointer has no advantage over unique_ptr.

If you want to take ownership from a unique_ptr and get a raw pointer that you own it's easy, call release(). You can't do that with a shared_ptr as there could be other owners and there's no way to force them to release their reference.

Clasen: Introducing GtkInspector

Posted May 19, 2014 12:16 UTC (Mon) by etienne (guest, #25256) [Link]

> C combines the power and flexibility of assembly language

Sometimes I wish we had a language at a lower level than C, something generic enough it does not define the number of bits of different types (nor their name), do not intrinsically define if a variable has a pointer to its type description, do not define what is a function or how to call them, do not define operator priority, do not define reserved names, but has the syntax to build that "sugar" by writing the source code for it - something a lot more like an interface to the assembler.

So that you could, at compile time, do:
#include <c++11.h> to write in that language, or:
#include <opencl.h> to write in that other language, or even:
#include <ada.h> or #include <java.h> at the beginning of the file.

Supporting without hacks common languages would be the first step, then you could extend it to support your own needs: multiple types of address pointers, functions which parameter types depends on parameter values, functions only visible on some conditions, your own memory allocation strategy, pre/post conditions...

That should also simplify inter-language calls.

Unfortunately I am not a good enough "computer language grammar" specialist to begin to describe it...

Clasen: Introducing GtkInspector

Posted May 19, 2014 14:03 UTC (Mon) by mathstuf (subscriber, #69389) [Link]

Ick. Could we at least kill the #include monstrosities (and the preprocessor in general). Modules have clearly been shown to be better by this time, haven't they?

Also, what you ask for sounds like lisp without parentheses…maybe indentation-aware like Python?

Sounds like what PL/1 was intended to be ...

Posted May 19, 2014 21:31 UTC (Mon) by Wol (guest, #4433) [Link]

In other words, Lisp with even more parentheses :-)

PL/1 is notorious for misplaced parentheses causing subtle bugs - the resulting code usually compiles but means something completely different from what was intended.

But it had a bunch of useful features like integer declarations typically declared the number of bits (and if you were nasty you could declare INT*10(3) which would declare three ten bit integers (and fit them into a 32-bit word, causing the run-time absolute conniptions as it tried to sort out the mess!! :-) Adding the key word ALIGNED iirc would then force them onto word boundaries, so it would probably - too long ago I can't remember - fit each INT*10 into a 16- or 32-bit word.

It was extremely useful in some respects, because you could - EASILY - choose between forcing the data layout how you wanted it, or letting the compiler decide what it wanted. Great for creating data files that could be passed between different computer architectures (until of course you hit big/little endian - things like the ICL 36-bit word were childs play :-)

Cheers,
Wol


Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds