operator<< для STL-контейнеров

Question:

Qt provides an overloaded operator << for different containers: QVector, QList, etc. For example, you can write like this:

QVector<QString> vec;
vec << QString("text1") << QString ("text2");

For STL containers, you can also implement something similar. For example, I wrote this code for std :: vector

template <class T, class E> std::vector<T>& operator<< (std::vector<T>& vec, const E& elem) {
    vec.push_back (elem);
    return vec;
}

You can write like this:

std::vector<int> vec;
vec << 54234 << 998;

Or even like this:

std::vector<std::string> vec;
vec << std::string ("word 1") << "word 2";

The convenience of such a thing is obvious. What do you think, what are the pitfalls in this approach? (I mean the general idea, not my specific code, although if you find bugs in it or a way to improve it, I will be very grateful)

Answer:

The syntax is convenient, but there are still pitfalls.

  • Consider the following code. It is clear that at the moment (1) 3 lines will be stored in the container, and this suits everyone.

     QVector<QString> v; v.append("Морж1"); v.append("Морж2"); v.append("Морж3"); // (1)
  • Further, programmer Ivan notes that this code can be simplified using a cool syntax:

     QVector<QString> v = QVector<QString>() << "Морж1" << "Морж2" << "Морж3"; ^
  • After that, Team Lead Illarion comes and sees that an unnecessary copying of the object is being performed in the place marked with a tick (let's assume that Illarion is not very aware of copy-on-write ).

  • But Illarion is aware of the use of constant references in similar cases – he knows that according to the standard ( [C++ Standard] - 10.4.10 Temporary Objects ), if a constant reference is initialized to a temporary variable, then the lifetime of this variable becomes equal to the lifetime of the link.

  • Based on his knowledge of the standard, the self-confident Illarion writes such code quite often, and everything works great for him:

     const QVector<QString>& ref = QVector<QString>(3, "Морж"); Q_ASSERT(ref.size() == 3);
  • And for the same reason, he doesn't really think about it when he edits Ivan's code to the next one and clicks the commit button:

     const QVector<QString>& v = QVector<QString>() << "Морж1" << "Морж2" << "Морж3";
  • Babakh!


  • Illarion receives an angry letter from the Continuous Integration system that his commit broke several tests. And he was also lucky that this code was covered by them!

  • And the point is this (this is the implementation of operator<< for QVector ):

     inline QVector<T>& operator<<(const T &t) { append(t); return *this; }
  • As you can see, the last of the operations ( << ) returns a reference ( QVector<T>& ) to the temporary QVector<QString> created earlier. Naturally, no one in this case will extend the lifetime of this variable – the last operation << during parsing does not (and does not need) information about where and how this variable was created.

  • Technically speaking, it is an rvalue from which we got an lvalue through an implicit conversion.

  • To summarize, const QVector<QString>& v is a dangling reference, the corresponding object has already been destroyed, and Hilarion could have lost his bonus if he had not written tests. Here's a story.

Scroll to Top