3

Why are some people cautious about using sender() in Qt programming? The typical answer is that it violates the principle of modularity of code. From the Qt documentation about pitfalls in PySide:

If you want to get the object that emitted a signal, you can do so using QtCore.QObject.sender(), although you should really think twice before using it (see the API docs of QObject for details). If you really, really decide that you have to use it, be aware that right now, you cannot call it as a static method, but only from within a QObject slot.

As a new PySide programmer, I wouldn't consider using sender in anything but a slot, so the second concern seems almost moot.

The first reason suggests examining the API of the doc. Let's look at that. From the API docs for sender:

This function violates the object-oriented principle of modularity.

This caveat is also mentioned in Summerfield's book on PyQt, where he writes

Some programmers don’t like using sender() because they feel that it isn’t good object-oriented style.

That's all he says.

What makes sender particularly non-modular compared to the rest of the standard practices in using PyQt/PySide/Qt? In PySide we are constantly invoking things like internalPointers in our model/view frameworks, or sneaking arguments to use in methods by making them attributes of self (effectively treating them as global variables). These and other such practices seem to technically violate modularity, and seem to be ubiquitous in GUI programming.

So why do people think sender deserves to be singled out as especially worrisome wrt modularity? Or perhaps equivalently, what is so modular about Qt programming anyway?

Overall, sender seems like an extremely useful, simple method. It is easier to use and teach others to use than things like partial functions or lambda expressions or QSignalMapper. So I can see the upside of using sender, and frankly have little grasp of the downsides that have merited their inclusion in official Qt documentation.

eric
  • 7,142
  • 12
  • 72
  • 138

1 Answers1

3

Generally speaking, an object's slot can be invoked by no signal at all, or by any compatible signal. In the former case, there is no such thing as a sender - for example, when using QMetaObject::invokeMethod. When the slot is activated through a signal, the sender object can be of any class. The most you can expect from the sender() is that it is-a QObject, if it is anything at all.

The signal-slot mechanism is designed to decouple objects, but by depending on the sender() you're reversing this and are coupling to the sending object. This has some applications when such coupling is expected by design, say when you implement a widget hider, or when you want to taylor the slot's action to the particular instance of a related object. Outside of such limited circumstances, where coupling-by-design is desirable, the use of sender() indicates bad design and coupling for no good reason.

The documentation merely means that you should understand the purpose of the signal-slot mechanism and the benefits of decoupling in software design before you choose to introduce coupling into your design.

Lambda expressions in fact completely obviate the need for the sender() and QSignalMapper, so you are arguing against yourself there:

connect(foo, &Foo::aSignal, [&foo]{ foo.method(); });
// as opposed to
connect(foo, &Foo::aSignal, [&foo]{ qobject_cast<Foo*>(sender())->method(); });

// and also
QList<QPushButton*> buttons;
QPointer<QLabel> label;
for (int i = 0; i < buttons; ++i)
  connect(buttons[i], &QAbstractButton::clicked, [i, label] {
    label->setText(QString("Button %i was clicked.").arg(i+1);
  });

On another note, the reasons for QSignalMapper's existence are a tad overstated anyway, since you've got the property system that you can leverage in Qt4-style code.

class Helper : public QObject {
  Q_OBJECT
  QPointer<QLabel> m_label;
public:
  Helper(QLabel * label, QObject * parent = 0) : QObject(parent), m_label(label) {}
  Q_SLOT void showIndex() {
    m_label->setText(
      QString("Button %i was clicked.").arg(sender()->property("index").toInt())
    );
  }
};

void setup(QList<QAbstractButton*> buttons) {
  int i = 1;
  foreach (QAbstractButton* button, buttons) {
    button->setProperty("index", i++);
    connect(button, SIGNAL(clicked()), helper, SLOT(showIndex());
  }
}

The "internal" pointer of the QModelIndex is merely a way to carry model-specific data in the index subject to following reasonable constraints at the time it was designed and released:

  1. broken C++ compilers (VC6!) that choked on some template uses,
  2. poor size optimization of linked binaries that further precluded template parametrization of consumers of the index type,
  3. pass-by-value semantics without overhead of a pimpl,
  4. no overhead of a vtable pointer,
  5. don't expose the hapless user to the object slicing problem.

It is a product of an era it was designed in. Sure it could be made better nowadays, without exposing the internalXxx methods to the wide public, but that's why those methods are called internal in the first place. Ultimately, if you really wish to shoot yourself in the foot, nobody's stopping you. As a consumer of the index, you're supposed to pretend that those methods aren't there. That's all there's to it.

Yes, even back in VC6 times, one could have used a dedicated pool allocator to do low-overhead pimpl for the index and similar classes. Said pool allocator could have even been leveraged to determine the runtime type of the derived classes and implement a virtual destructor mechanism without the vtable pointer overhead. Sure. But it wasn't done that way, and the decision is with us. Given the amount of explaining needed for the model implementer to safely leverage such "magic", I think the internal pointer mechanism is a tad safer bet.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • thanks. I understood some of that, especially the first couple of paragraphs (I am PySide so some of the more pure C++ Qt idioms are a bit above me). I didn't realize you could call a slot w/o a sender, which is really helpful. In terms of lambda expressions, I realize that they let you avoid `sender`. My point was that for starting out, `sender` is simpler (lazier?) than having to teach people about `lambda` functions. "As a consumer of the index, you're supposed to pretend that those methods aren't there. That's all there's to it." :) In Python we tend to ignore public/private. :) – eric Jul 28 '14 at 18:18
  • 1
    @neuronet Lambda syntax is a tool that you can leverage to write better code. In modern C++11 code, as well as in modern python, it is essential to know about it if you wish to write concise asynchronous code. The "people" are supposed to know about lambda syntax before they attempt anything serious, and certainly before they are unleashed on production code of any sort :) – Kuba hasn't forgotten Monica Jul 28 '14 at 20:06