[Development] templated QObjects [was: Re: We are planning to upgrade qdoc to use clang for parsing C++]

Thiago Macieira thiago.macieira at intel.com
Sat Feb 27 00:56:08 CET 2016


On sexta-feira, 26 de fevereiro de 2016 20:30:28 PST Milian Wolff wrote:
> >  The main problems of templated QObject are captured more or less in this
> > 
> > thread:
> > http://lists.qt-project.org/pipermail/development/2013-March/010288.html
> > 
> >  Personally I still think it would be a fancy feature, a bit dangerous to
> > 
> > implement maybe even dangerous to use, but really cool :-D
> 
> Thanks for the link. How often is the MOC layout changed in an ABI
> incompatible way?

There's no historical pattern. There was a major break from Qt 3 to 4 and 
smaller one from 4 to 5. Qt 4 did have updates that didn't break the ABI; the 
Qt 5.0 update removed the ability to read meta objects created with Qt 4 moc. 
I don't remember how the 1→2 and 2→3 updates happened (Qt 3 still used 
register-on-load meta objects).

But since the meta object itself has a version number and the only code that 
ever reads the internal data is inside QtCore, so we could make changes that 
change the layout.  We haven't done that: all changes between major releases 
have added things without changing the layout.

That's the structure layout though. The file itself compares the 
Q_MOC_OUTPUT_REVISION macro for equality (not ordering).

> I.e. what problems would we get from having to install the
> moc files?

Lots.

First of all, note that you're asking that
 a) installing generated code
 b) including such generated code from your public headers

That means the headers do not compile until moc has generated its output. Moc 
is currently able to ignore missing includes and we need that when parsing 
.cpp files that contain Q_OBJECT. But doing that in headers is just... ick.

There's also the fact that now the generated code becomes part of your public 
ABI and will be compiled by your users. That means the code from moc needs to 
compile with their compiler settings, whichever that may be. And we would need 
to be very careful in how we change moc, because we need to keep users' 
compatibility requirements when they upgrade Qt. Their requirements could be 
stricter than Qt's (to a point).

If we talk simply about non-templated QObjects, there are only drawbacks. The 
first is that the current output produces functions and data which would end up 
in *each* and *every* translation unit that included the output. You'd get 
linker errors. We could fix this by marking all the functions as inline, but we 
can't fix the data.

This includes the most important data member of all: const QMetaObject * 
ClassName::staticMetaObject. It's a class-level static, so it needs to be 
defined in a single .cpp and nowhere else. Period, no fix possible.

So the discussion ends here for non-template classes, at least without 
breaking Qt API.

If we were willing to break Qt API, we could remove the staticMetaObject class 
member and make it static inside a member static inline function. Static data 
inside inline functions need to be merged by the linker and the dynamic 
linker.

This however produces data bloat and reduces performance. For one thing, 
instead of getting the address of the meta object directly, you need to call a 
function that will return the address. For another, since we created work for 
the dynamic linker, now it needs to resolve a named relocation in places it 
didn't before. And there's still the fact that the meta object is present in 
every shared object / DLL that it got used from, even if at runtime only one 
copy is ever used.

And then there are the problems of dynamic linkers actually merging static 
data inside inline functions at runtime. This is an area where several ABIs 
fall short and that's assuming that they even try. I have one word for you 
here: dynamic_cast.

And then there are templates.

If we talk about template classes, then we have more problems. First, note the 
bloating and runtime-deduplication problem I mentioned above. They'd apply 
here. Though note that you can declare static data of template classes in 
headers, so we wouldn't need to break the Qt API just for this.

The next problem is the meta object layout. Currently, it's optimised to 
contain static strings (an array of QByteArrayLiteral data in Qt 5, one long 
const char array in Qt 4, which I'll bring back for Qt 6). Obviously, the name 
of the instantiated class isn't constant, so the data can't be constant 
either. It stands to reason that the use of a templated class in the first 
place is the ability to use the template parameters in reflected members 
(signals, slots, invocables, properties, enums, classinfo), so the types and 
signatures for those members would need to change.

Then we go back to whether we can keep the current Qt API: if the data isn't 
constant, can we even return const char* from the QMetaObject member functions 
like we do? If we're going to calculate the strings at runtime, are we even 
able to? What happens for:

	signal:
		void somethingHappened(
			typename std::enable_if<std::is_default_constructible_v<T>, 
				typename std::remove_cv<T>::type>::type);

How can QMetaObject understand what that type will be?

Or we just say "the hell with it" and keep the template names in the class 
name and reflected members. Of course, you can't connect that signal above to 
SLOT(doSomething(int)) even if T was int. (no such problem with the new 
connect style)

The above also points to another problem: signals need to be reflected, so you 
can't use std::enable_if like I did. 

Then we need to look into template members (whether in a template class or 
not). 

They simply can't be extracted for reflection. So a template slot would never 
be usable in the SLOT() macro and template invocables would never be callable 
from dynamic bindings (QML, QtDBus, QtScript). And there couldn't be any 
template signals without a major overhauling of the way that signals are 
identified. See my blog "the future of moc" from 2012 for more information on 
the signal identification problem.

To work around that, we'd need an explicit list of which template members we'd 
like to be extracted for reflection in the meta object.

Have I convinced you? I'm only getting warmed up. I'm sure I can find more 
issues.

> Alternatively: couldn't moc re-create the required data from included files
> when they contain templated objects? That would solve the problem as well,
> no?

I have no idea what you meant here.

Or, well, I do have one, but I don't think that I understood correctly what 
you're suggesting. I understood that we keep a copy of the header file's source 
in the target application and run moc at runtime to dynamically create the 
meta object, on the fly.

Since I don't think that's what you're suggesting and since the above has so 
many problems (starting with the fact that it doesn't resolve the problem), 
I'm not even going to analyse it.

Can you clarify what you meant?

-- 
Thiago Macieira - thiago.macieira (AT) intel.com
  Software Architect - Intel Open Source Technology Center




More information about the Development mailing list