Liebe Leserinnen, liebe Leser,

während ich heute eine Klasse von Qt, nämlich QImage, durchgesucht habe, habe ich auf eine interessante Besonderheit von C++ gestoßen. Diese Klasse hat zwei Klassenfunktionen, die meine Aufmerksamkeit auf sich gelenkt haben:

C++

1
2
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) const &;
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) &&;

Wie es offensichtlich ist, haben beide Funktionen Ref-Bezeichnungen am Ende der Deklarationen, die ich aufgrund meiner vorigen Erfahrung (die bisher nicht so viel ist) als etwas gar Ungewöhnliches verbucht habe. Was sind diese Sachen und was machen sie eigentlich?

Zuallererst ist das eine gute Idee, sicherzustellen, was sie nicht sind:
Die Ref-Bezeichnung am Ende einer Funktion

  • wird das Typ des Rückgabewerts nicht ändern, und haben damit nichts zu tun.
  • wird das Typ von *this nicht ändern, und gemäß des Standards ist das immer ein L-Wert

Als es schon erklärt wurde, was diese Ref-Bezeichnungen nicht machen, ist es an der Zeit zu entdecken, was sie machen.

Anscheinend, wie es mit sehr vielen Sachen bei C++ ist, helfen sie bei der Auflösung der Funktionsüberladung. Die zweite Funktion, die nur eine Überladung der ersten Funcktion ist, hat über seinen eigenen Character einige Hinweise schon gegeben, aber für ungeübte Augen ist die ganze Sache noch zu vernebelt. Ein kurzes Beispiel kann das besser zeigen, wie diese Bezeichnungen den Funktionsaufruf beeinflussen:

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream> 

struct MyStruct
{
    void funct() &
    {
        std::cout << "MyStruct was lvalue this time." << std::endl;
    }
    
    void funct() &&
    {
        std::cout << "MyStruct was rvalue this time." << std::endl;
    }
};

int main()
{
    MyStruct ms;
    
    ms.funct();
    MyStruct().funct();
}

Wie das Beispiel verdeutlicht, ist der erste Aufruf von funct() aus einem genannten Objekt gemacht, und der zweite Aufruf ist aus einem temporären Objekt gemacht. Die Ergebnis ist wie erwartet.

Shell

1
2
MyStruct was lvalue this time.
MyStruct was rvalue this time.

Also, wenn eine Ref-Bezeichnung am Ende der Funktionsüberladung verwendet wird, fungiert es dem Compiler als ein Hinweis. Dann wird der Compiler basierend auf dem Status von L- oder R-Wert des Objekts, aus dem die Funktion aufgerufen wurde, die richtige Funktion aufrufen. Diese Fähigkeit eröffnet natürlich weitere Möglichkeiten für Optimierung. Meiner Meinung nach könnte jeder zustimmen, dass es ein sehr nettes Merkmal ist, deshalb möchte ich weitere interessante Beispiele zeigen, um dieses Merkmal besser zu erklären.

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream> 

struct MyStruct
{
    void funct() &
    {
        std::cout << "MyStruct was lvalue." << std::endl;
    }
    
    void funct(int arg) &
    {
        std::cout << "MyStruct was lvalue and had a by-value argument " << arg << std::endl;
    }
        
    void funct() const &
    {
        std::cout << "MyStruct was lvalue and const." << std::endl;
    }

    void funct(const int& arg) const &
    {
        std::cout << "MyStruct was lvalue, const and had a const lvalue reference argument " << arg << std::endl;
    }
    
    void funct(int&& arg) const &
    {
        std::cout << "MyStruct was lvalue, const and had an rvalue reference argument " << arg << std::endl;
    }
    
    void funct() &&
    {
        std::cout << "MyStruct was rvalue." << std::endl;
    }
    
    void funct(const int& arg) &&
    {
        std::cout << "MyStruct was rvalue and had a const lvalue reference argument " << arg  << std::endl;
    }
};

int main()
{
    int myNum = 5;
    
    MyStruct ms;
    
    const MyStruct& msRef = ms;
    
    ms.funct();
    ms.funct(3);
    msRef.funct();
    msRef.funct(myNum);
    msRef.funct(3);
    MyStruct().funct();
    MyStruct().funct(myNum);
}

Aus dem Beispiel ist ersichtlich, dass einem nichts verhindert, diesen Art von Ref-Bezeichnungen mit anderen (gewöhnich platzierten) Ref-Bezeichnungen und CV-Bezeichnungen zu kombinieren. Weiterhin, wenn man es so will, ist es möglich eine Funktionsüberladung für fast alle Situationen zu bereitstellen. Die Ergebnisse des Beispiels sind unten:

Shell

1
2
3
4
5
6
7
MyStruct was lvalue.
MyStruct was lvalue and had a by-value argument 3
MyStruct was lvalue and const.
MyStruct was lvalue, const and had a const lvalue reference argument 5
MyStruct was lvalue, const and had an rvalue reference argument 3
MyStruct was rvalue.
MyStruct was rvalue and had a const lvalue reference argument 5

Nicht alle Möglichkeiten sind gezeigt, aber ich denke, dass es das Argument schon angebracht hat. Man könnte eine Funktionsüberladung für wirklich alle Status, in dem das Objekt oder das versorgte Objekt ist, haben, das mal wieder demonstriert, wie flexibel C++ ist. Natürlich gibt es einige Beschränkungen, wie es mit vielen anderen Merkmalen ist, die man nicht vergessen soll: wenn man Ref-Bezeichnungen auf diese Weise benutzen will, können sie auf Funktionsüberladungen nicht zufällig platziert werden. Entweder alle Überladungen haben Ref-Bezeichnungen, oder keine. Der unten zu sehende Code wird nicht kompiliert werden:

C++

1
2
3
4
5
6
struct MyStruct {
    
    void funct();
    void funct() &&;

};

Die Fehlermeldung des Compilers ist unten zu lesen:

Shell

1
2
3
4
5
6
error: ‘void MyStruct::funct() &&’ cannot be overloaded with ‘void MyStruct::funct()’
     void funct() &&
          ^~~~~
note: previous declaration ‘void MyStruct::funct()’
     void funct() {
          ^~~~~

Zusammenzufassend kann ich das sagen, dass obwohl Ref-Bezeichnungen am Ende der Funktionsdeklaration ein sehr nettes Merkmal ist, wird darüber in nicht so vielen Bücher diskutiert, besonders in Bücher, die ausdrücklich um C++ umgehen, z.B. Effective Modern C++ von S. Meyers. Ich war ein bisschen überrascht, aber ich darf nicht entscheiden, was diese Bücher einschließen sollen. Nichtsdestoweniger können interessierte Leser über dieses Thema in diesem Beitrag mehr lesen. Das ist alles für heute, hoffentlich haben Sie dieses Text genießen.

Vielen Dank fürs Lesen.