Wie übergebe ich ein Array an eine Klasse in C++?

Peter_2498

Lieutenant
Registriert
Apr. 2018
Beiträge
583
Hallo,

ich bin gerade dabei nach langer C++ Abstinenz mal wieder mein Wissen aufzufrischen, da ich mit dieser Sprache ein Projekt für die Uni programmieren will und ja hier und da hakt es noch ziemlich bei den Basics. Aus diesem Grund wollte ich hier mal einige Dinge wissen.

Wie kann ich ein Array einem Objekt/einer Klasse übergeben?
Wenn ich eine Klasse habe, welche ein Array als Member hat, wie genau initialisiere ich über den Konstruktor so ein Objekt? Bisher habe ich es nur mit den normalen Arrays, also "int Arr[ ]" versucht. Sollte man sowas eventuell mit std::Array oder std::Vector machen?
 
Es kommt halt immer darauf an, was du haben willst. Eine pauschale Antwort ist aus meiner Sicht nicht möglich.
 
Peter_2498 schrieb:
Bisher habe ich es nur mit den normalen Arrays, also "int Arr[ ]" versucht. Sollte man sowas eventuell mit std::Array oder std::Vector machen?

SL.con.1: Prefer using STL array or vector instead of a C array​

Reason​


C arrays are less safe, and have no advantages over array and vector. For a fixed-length array, use std::array, which does not degenerate to a pointer when passed to a function and does know its size. Also, like a built-in array, a stack-allocated std::array keeps its elements on the stack. For a variable-length array, use std::vector, which additionally can change its size and handles memory allocation.
https://isocpp.github.io/CppCoreGui...sing-stl-array-or-vector-instead-of-a-c-array

Peter_2498 schrieb:
Wie kann ich ein Array einem Objekt/einer Klasse übergeben?
Wenn ich eine Klasse habe, welche ein Array als Member hat, wie genau initialisiere ich über den Konstruktor so ein Objekt?

C.49: Prefer initialization to assignment in constructors​

Reason​


An initialization explicitly states that initialization, rather than assignment, is done and can be more elegant and efficient. Prevents “use before set” errors.
https://isocpp.github.io/CppCoreGui...-initialization-to-assignment-in-constructors
 
  • Gefällt mir
Reaktionen: Peter_2498, BeBur, C:\Defuse_Kit und eine weitere Person
Du kennst die Größe zur Compile-Time: std::array<T,N>
Du kennst die Größe nur zur Laufzeit: std::vector<T>
Du kennst die Größe nicht: 🙄🙈

Falls du wegen der Performance Sorgen hast: Im Zweifelsfall kannst du std::vector mit std::move (oder a.swap(b)) quasi kostenlos verschieben (das ist natürlich dann eine nicht-const-Operation und leert den original-std::vector).
 
  • Gefällt mir
Reaktionen: Peter_2498 und BeBur
Grundsätzlich kann ich nur +1 geben zu dem Rat, std::vector zu benutzen. Oder std::array, wenn du dessen Eigenschaften denn wirklich brauchst.

Das hier wäre ein minimales Beispiel:
Code:
#pragma once

#include <vector>

class MyClass
{
private:
    std::vector<int> m_numbers{};
};

Zur Erläuterung:
  • Durch Benutzen von "{}" wird der Vector initialisiert, man muss das dann nicht mehr im Konstruktor tun. Könnte man aber, das ist kein Problem. Du sparst dir nur bei solch trivialen Sachen das Schreiben eines Konstruktors.
  • Da überhaupt kein Konstruktor angegeben wird, musst du auch keine schreiben, das übernimmt dann der Compiler für dich.
  • Es gibt bislang keine public Funktionen, außer der automatisch generierten Konstruktoren. Der Code, so wie er ist, macht eigentlich nicht viel.

Hier zwei Alternativen, die etwas näher an der Praxis sind:
Code:
#pragma once

#include <vector>

class MyClass
{
public:
    MyClass() = default; # Notiz 1
    MyClass(const std::vector<int>& numbers) : m_numbers(numbers) {} // Variante 1
    MyClass(std::vector<int> numbers) : m_numbers(std::move(numbers)) // Variante 2

private:
    std::vector<int> m_numbers{}; # Notiz 1
};

Zur Erläuterung:
  • Notiz 1: Wir können die Klasse erstellen, ohne, dass wir einen Vector rüberreichen, und zwar mittels des Default Konstruktors. Tun wir das, wollen wir unseren Vector trotzdem initialisiert haben, daher wieder das "{}". Es würde auch ohne gehen, ist aber "Good Practice" und ein paar C++ Cracks können dir sicher sagen, warum. Als weitere Bemerkung dazu: Möchtest du verhindern, dass die Klasse ohne übergebenen Vector erstellt werden kann, kannst du den Default Konstruktor auch mittels "= delete" löschen. Und noch etwas: Dieses Mal schreiben wir den Default Konstruktor, weil der Compiler diesen eben nicht mehr automatisch generiert, weil wir die beiden anderen Konstruktoren angegeben haben. Das ist recht verwirrend, zugegeben. Siehe dazu diesen Link: https://mariusbancila.ro/blog/2018/07/26/cpp-special-member-function-rules/
  • Variante 1: Wir übergeben den Vector als const Reference, sichern damit also zu, dass wir den hineingegebenen Vector nicht verändern. Den Member Vector der Klasse selbst initialisieren wir mit dem hineingegebenen Vector - wir machen also eine Kopie.
  • Variante 2: Wir übergeben den Vector als Value, machen also direkt eine Kopie und initialisieren den Member Vector mittels eines move, es wird also keine weitere Kopie gemacht. Stichworte sind sink arguments und copy and move idiom.
Jetzt könntest du auch sagen: Hey, ich will mir den hereingegebenen Vector nicht selbst "ownen", sondern einen globalen Vector manipulieren oder gar nur anschauen. Das geht so:

Code:
#pragma once

#include <vector>

class MyClass
{
public:
    MyClass(const std::vector<int>& numbers) : m_numbers1(numbers) {} // anschauen, weil const
    MyClass(std::vector<int>& numbers) : m_numbers2(numbers) {} // manipulieren, keine const Garantie

private:
    std::vector<int>& m_numbers1; // wir speichern nur eine Referenz
    std::vector<int>& m_numbers2; // wir speichern nur eine Referenz
};

Zur Erläuterung:
Wie schon kommentiert, speichern wir nur eine Referenz auf den Vector, kein Vector-Objekt selbst. Je nach const Zusicherung kann der referenzierte Vector manipuliert werden oder eben nicht. Manipulieren geht nur, wenn der hereingegebene Vector selbst nicht als const deklariert ist.
Achtung bei dieser Variante: Der hereingegebene Vector, den wir referenzieren, könnte "out-of-scope" gehen und gelöscht werden, darauf bitte aufpassen. Sonst crasht dein Programm beim nächsten Zugriff auf die Referenz von MyClass.
Achja, und du kannst MyClass nicht erzeugen, ohne eine Referenz auf einen Vector hineinzugeben, denn Referenzen dürfen nicht null sein.

Was nicht abgedeckt wurde:
  • Du kannst den Vector auch als Pointer auf einen Vector hineingeben
  • Du kannst den Vector auch als R-Value-Referenz hineingeben
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Peter_2498 und KitKat::new()
Danke für das umfangreiche Beispiel @m.c.ignaz . Ich hätte mal paar Fragen dazu:

1. Wieso brauchen wir überhaupt verschiedene Konstruktoren?

Bisher habe ich eigentlich einfach einen Konstruktor geschrieben, der die Member initialisiert. Wieso braucht man noch einen Default Konstruktor bzw. wieso würde ich ein Objekt überhaupt erzeugen wollen OHNE einen Vektor zu überreichen?

2. Wieso braucht der Compiler einen Default-Konstruktor, wenn er schon einen vom Benutzer definierten Konstruktor hat ?

3.Sind folgende zwei Ausdrücke gleichwertig?



C++:
const std::vector<int>& m_numbers1 = numbers
std::vector<int>& m_numbers1 = const std::vector<int>& numbers

Wenn ich also eine Referenz A mit einer anderen Referenz B initialisiere, dann sind A und B doch im Grunde gleich oder? Beide zeigen auf dasselbe Objekt und haben auch die gleichen Eigenschaften, was Veränderbarkeit anbelangt.

4.Wie kann ich Methoden aufrufen, wenn ich das Objekt nur als Template in meiner Klasse habe?

Ich habe ein Problem mit meinem Code. Ich habe eine Klasse AA definiert, welche als Member einen Vektor Arr ,bestehend aus Objekten mit Datentyp T(Template), besitzt. Alle Vektoren, die ich in Zukunft da reingeben möchte, besitzen Objekte, welche eine Kostenfunktion besitzen. Ich würde in Klasse AA dann gerne folgenden Ausdruck implementieren:

C++:
template<typename T>
class AA{
public:
    std::vector<T>& Arr;

    AA(std::vector<T> Arri) : Arr(Arri) {}

    virtual bool Kosten() {
        if (Arr[0].Kosten() == 5)  
            return true;
        else
            return false;
    }

};

Arr[0] müsste ja ein Objekt auswählen und über den Punkt-Operator dann die Methode. So wie ich das versucht habe, geht das natürlich nicht. Der Code ist nur zum Üben, hat keine großartige Sinnhaftigkeit für mein Programm. Sieht für mich nach einem Grund für ein Interface aus?
 
Zuletzt bearbeitet: (Code für vierte Frage wurde ergänzt.)
Wie sieht denn die Template-Definition aus? Also wie ist Arr genau deklariert und wie sieht das zugehörige Template aus?
 
Peter_2498 schrieb:
Bisher habe ich eigentlich einfach einen Konstruktor geschrieben, der die Member initialisiert. Wieso braucht man noch einen Default Konstruktor bzw. wieso würde ich ein Objekt überhaupt erzeugen wollen OHNE einen Vektor zu überreichen?
Code:
class Object{
Object(const std::vector<int>&a);
}
...
std::vector<Object>objs;
...
Geht nicht, da Object nicht default-constructible ist. Du brauchst oft ein "leeres" Object, dass du einfach so erzeugen kannst. Falls das nicht geht (oder nicht gewollt ist), musst du dann mit (smart-)Pointern arbeiten.
std::vector braucht ein default-constructible n Typ, da es Methoden hat wie std::vector::resize, was ja irgendwie Elemente erstellen muss, wenn der Vektor dadurch wächst.


Peter_2498 schrieb:
Wieso braucht der Compiler einen Default-Konstruktor, wenn er schon einen vom Benutzer definierten Konstruktor hat ?
Weil sonst kein Objekt einfach so erzeugt werden kann.
Code:
Object a;
geht dann nicht mehr. Weil wie will man a erzeugen, wenn es kein Object() gibt. (siehe oben)


Peter_2498 schrieb:
Sind folgende zwei Ausdrücke gleichwertig?
Also das eine ist const T&, das andere ist T&, also nicht was const-correctness angeht. Die Zeile 2 sieht vor allem nicht nach gültigem C++ aus.


Peter_2498 schrieb:
So wie ich das versucht habe, geht das natürlich nicht.
Wirklich, das folgende kompiliert bei mir
Code:
#include <vector>
template<typename T>
class AA{
public:
    std::vector<T>& Arr;
    AA(std::vector<T> Arri) : Arr(Arri) {}
    virtual bool Kosten() {
        if (Arr[0].Kosten() == 5)  
            return true;
        else
            return false;
    }
};
class K{
public:
    int Kosten(){
        return 5;
    }
};
int main(){
    AA<K> a{{K()}};
    return a.Kosten();
}
 
Hmm... also ich habe bei mir ein Vektor aus 3 Objekten der anderen Klasse initialisiert und dann die Klasse AA damit initialisiert. Er zeigt mir die ganze Zeit in dieser Zeile (bei dir @Hancock Zeile 8)den Fehler: 'Links von ".Kosten" müssen sich in einer Klasse/Struktur/Union befinden'.
 
Peter_2498 schrieb:
1. Wieso brauchen wir überhaupt verschiedene Konstruktoren?
Peter_2498 schrieb:
2. Wieso braucht der Compiler einen Default-Konstruktor, wenn er schon einen vom Benutzer definierten Konstruktor hat ?
Kurz: Weil es verschiedene Arten gibt, auf die man ein Objekt erzeugen kann. Diese Konstruktoren sind sog. "Special Member Functions".

Ein kleines, motivierendes Beispiel:
Code:
#pragma once

#include <string>

class Person
{
public:
    Person() = default; // default C'tor, ist erstmal nur drin, damit das Beispiel kompiliert
    Person(std::string name, int age) : m_name(std::move(name)), m_age(age) {} // benuterdefinierter C'tor

private:
    std::string m_name{""};
    int m_age{0};
};

int main()
{
    // Welcher Konstruktor wird jeweils benutzt?
    Person p0;
    Person p1("Bob", 37);
    Person p2(p1);
    Person p3 = p1;
    Person p4(Person("Alice", 33));
    Person p5 = std::move(p4);
}

Es ist intuitiv zu denken: Ich habe einen Konstruktor (Zeile 9) angegeben, also ist nun definiert, wie ein Objekt vom Typ Person zu erzeugen ist. Leider ist es, wie immer, nicht so einfach.
Dir wird bereits selbst auffallen, dass sich die Zeilen 19 bis 24 nur einmal an den von dir vorgegebenen Bauplan halten. Dennoch sind alle anderen Arten völlig valide und funktionieren.
Der Grund sind die "Special Member Functions", die der Compiler für dich implizit erzeugt hat. Bis auf den Default Konstruktor, den ich deshalb selbst geschrieben habe. (Siehe hier) Um Missverständnissen vorzubeugen: Das = default bedeutet nicht, dass es der Default Konstruktor ist, sondern dass eine Default Implementierung angegeben werden soll.

Die Special Member functions sind: (Siehe hier ganz unten)
  • Default Constructor
  • Copy Constructor
  • Copy Assignment Operator
  • Move Constructor
  • Move assignment Operator
  • Destructor
Darüberhinaus kannst du weitere, benutzerdefinierte Konstruktoren angeben.

Nun zu deiner Frage, warum der Compiler den Default Konstruktor braucht: Der Compiler braucht den Default Constructor nicht. Brauchen tut er keine dieser Special Member Functions. Er geht aber davon aus, dass du sie brauchst, weil du deine Klasse möglichst vielfältig erzeugen können möchtest.
Du kannst aber das Verhalten Steuern bzw. das, was deine Klasse repräsentieren soll. Nehmen wir einmal an, dass eine erzeugte Instanz deiner Klasse etwas einzigartiges repäsentieren soll, z.B. eine Verbindung zwischen zwei bestimmten IP-Adressen. Ist das etwas, das man kopieren können soll? Die Verbindung besteht ja nur einmal. Du kannst jetzt deinen Copy Constructor und Copy Assignment Operator mittels = delete löschen. Somit kann eine Instanz deiner Klasse nur noch "gemoved" werden, nicht mehr kopiert. (Okay, das Beispiel ist etwas lame, aber ich hoffe es wurde klar)

Wenn deine Klasse etwas komplexer sind, kannst du durch eigene Implementierungen dieser Special Member Functions sauber entsprechende Erzeugunsvorgänge beschreiben. Das schafft der Compiler dann nicht mehr automatisch und/oder fehlerfrei. Eine Move-Konstruktion ist eben etwas anders als eine Kopie zu erstellen.

Warum will man überhaupt eine Instanz einer Klasse erstellen, ohne einen Vector rueberzureichen?
Das ist bei einem solch simplen Beispiel eine berechtigte Frage, es kommt wie immer auf deinen konkreten Fall an. Stell dir vor, du hast eine Klasse, die 10 oder 15 Member Variablen enthält. Und für die es nicht wichtig ist, dass alle gesetzt sind, um zu "funktionieren", sondern eher Zusatzinformationen enthält oder alternatives Verhälten abbildet. (Man könnte fragen: Wenn Member7 gesetzt ist, tue dies, wenn nicht, tue jenes)

Was du nun anschauen müsstest, um das in Gänze zu verstehen:
  • Wie siehen die Signaturen der Special Member Functions aus?
  • Was ist eine R-Value-Referenz (die Move (Assignment) C'tors benutzen diese)
  • Beispielimplementierungen
  • Wann wird welcher C'tor aufgerufen (das ist nicht immer offensichtlich, ganz im Gegenteil ;-))
 
  • Gefällt mir
Reaktionen: Peter_2498 und Drexel
@Hancock

Peter_2498 schrieb:
3.Sind folgende zwei Ausdrücke gleichwertig?


C++:
const std::vector<int>& m_numbers1 = numbers
std::vector<int>& m_numbers1 = const std::vector<int>& numbers
Wenn ich also eine Referenz A mit einer anderen Referenz B initialisiere, dann sind A und B doch im Grunde gleich oder? Beide zeigen auf dasselbe Objekt und haben auch die gleichen Eigenschaften, was Veränderbarkeit anbelangt.
Hancock schrieb:
Also das eine ist const T&, das andere ist T&, also nicht was const-correctness angeht. Die Zeile 2 sieht vor allem nicht nach gültigem C++ aus.

Ist die zweite Zeile nicht genau das, was im Code-Beispiel von @m.c.ignaz passiert bei der Initialisierung in Zeile 8? Hier ist nochmal der Code:

C++:
#pragma once

#include <vector>

class MyClass
{
public:
    MyClass(const std::vector<int>& numbers) : m_numbers1(numbers) {} // anschauen, weil const
    MyClass(std::vector<int>& numbers) : m_numbers2(numbers) {} // manipulieren, keine const Garantie

private:
    std::vector<int>& m_numbers1; // wir speichern nur eine Referenz
    std::vector<int>& m_numbers2; // wir speichern nur eine Referenz
};

Jetzt nochmal zu meinem Code der ja irgendwie nicht funktioniert. Ich habe mal die komplette Datei gepostet:

C++:
#include <iostream>
#include <string>
#include <array>
#include <vector>


class eObjekt {
public:
    int w;

    eObjekt(int a) {
        w = a;
    }

    int Kosten() {
        return w + 3;
    }
};

template<typename T>
class AA {
public:
    std::vector<T>& Arr;

    AA(std::vector<T> Arri) : Arr(Arri) {}

    virtual bool cost() {
        if (Arr[0].Kosten() == 5)
            return true;
        else
            return false;
    }

};


int main()
{
    //std::vector<int>Li = {1, 2, 3, 4, 5};
    //AA<int> Fata = AA<int>(Li);
    eObjekt ee = eObjekt(2);
    eObjekt ee2 = eObjekt(4);
    eObjekt ee4 = eObjekt(1);
    std::vector<eObjekt>Li2 = {ee,ee2,ee4};

    AA<eObjekt> Fata2 = AA<eObjekt>(Li2);

    //std::cout << (Fata2.Arr)[0].Kosten() << std::endl;    //Das kompiliert bei mir nicht
    //std::cout << Fata2.cost() << std::endl;                //Das kompiliert auch nicht
   

}
 
Peter_2498 schrieb:
Ist die zweite Zeile nicht genau das, was im Code-Beispiel von @m.c.ignaz passiert bei der Initialisierung in Zeile 8?
Nein. Weil
Code:
T&a=const T& b;
kein gültiges C++ ist.
Code:
T&a=(const T&)b;
wäre es, jedenfalls syntaktisch, semantisch ist es aber immer noch nicht OK, da du const T& nicht nach T& casten kannst ohne C-Style/reinterpret_/const_ cast.
'int main()':
<source>:27:32: error: binding reference of type 'std::vector<int>&' to 'const std::vector<int>' discards qualifiers
27 | std::vector<int>& m_numbers2 = (const std::vector<int>&) numbers;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MyClass compiliert nicht, weil
1. Konstruktor: "discards Qualifier"
2. Konstruktor: "uninizialized reference member (-fpermissive)"

Dein Kompletter Code funktioniert bei mir einwandfrei. Z. 48 wie Z. 49. Kannst ja hier im Godbolt sehen. https://godbolt.org/#z:OYLghAFBqd5Q...jyOcUs5N9bCXKmVSwpHAzC8t4OcylW9SBXDPLYyQQA===

Mein Tipp an dich: Keine Referenzen.
Mein zweiter Tipp: Keine Referenzen!
Mein dritter Tipp: Es gibt eine Ausnahme: Funktionen können Parameter als const T& erhalten. (Nicht T&).
 
Hancock schrieb:
Dein Kompletter Code funktioniert bei mir einwandfrei. Z. 48 wie Z. 49. Kannst ja hier im Godbolt sehen. https://godbolt.org/#z:OYLghAFBqd5Q...jyOcUs5N9bCXKmVSwpHAzC8t4OcylW9SBXDPLYyQQA===
Also bei mir läuft der Code mit dem Member als Referenz(so wie ich es gepostet habe) nicht. Visual Studio kommt mir da mit einem "Debug Assertion Failed" und "Vector subscript out of range". Verstehe nicht wirklich wie das sein kann...

Wenn ich allerdings den Member nicht als Referenz sondern als Objekt an sich schreibe, dann funktioniert alles, wenn man denn so schlau ist die ersten beiden Zeilen von Main auszukommentieren/ zu löschen, weil das int-Objekt natürlich keine Kostenfunktion enthält😑.
 
Also mit "funktioniert" meine ich, dass er kompiliert. Nicht dass das Ergebnis korrekt ist :D.

Ich hab den Fehler gefunden. Du erzeugst eine Referenz auf Arri, das ist trommelwirbel eine Kopie von Li2. Nach dem Aufruf des Konstruktors wird diese Kopie zerstört. Dadurch ist die Referenz ungültig => Undefined Behaviour (UB).

Ergo: Keine Referenzen.

Würdest du wirklich ne Referenz von Li2 wollen, müsstest du !überall! eine Referenz erzwingen. Also auch bei den Parametern des Konstruktors.
 
  • Gefällt mir
Reaktionen: Peter_2498
Ich hätte noch eine weitere Frage. Ich habe ja oben meine Klasse AA und möchte nun eine Funktion f schreiben, die alle Objekte entgegennimmt, welche von der "Form" eines Objekts aus AA sind (beinhalten als Member ein Array von spezifischen Objekten(-> Template) und eine Kostenfunktion). Ich hätte das nun folgendermaßen gemacht:

  1. Ich mache aus Klasse AA eine abstrakte Klasse
  2. f bekommt als Parameter eine Referenz auf die abstrakte Klasse (abstrakte Klassen können ja nicht instanziiert werden)
Wenn ich nun eine weitere Klasse BB von Klasse AA mit dem entsprechenden Template Typ erben lasse, dann kann ich die ja somit problemlos an f übergeben und f selbst muss nur wissen, wie das Objekt AA aussieht. Macht das Sinn? Die Klasse AA wäre damit eine Art Interface oder?
 
Peter_2498 schrieb:
Macht das Sinn? Die Klasse AA wäre damit eine Art Interface oder?
Ja, macht in etwa Sinn.

Eine eventuelle Alternative wären Templates mit Concepts, welche auf Compiletime Polymorphismus setzen würden statt auf Runtime Polymoprhismus.
 
  • Gefällt mir
Reaktionen: Peter_2498
Peter_2498 schrieb:
f bekommt als Parameter eine Referenz auf die abstrakte Klasse
Hmm, hört sich schon mal verdächtig nach versteckten Zeigern an, die (weil versteckt) schon längst ungültig sind.

Die Signatur von f ist?
Code:
return_type f(decltype(AA<T>::Arr::value_type)&v);
oder
Code:
return_type f(AA<T>&aa);
Beides sieht nicht sauber aus. Das erste ist quasi f(T&v) und T ist abstrakt, dann doch besser f(T*v).
AA<T> geht sowieso nicht mit abstrakten Klassen "T" (Stichwort splicing).
AA<T*> geht mit abstrakten Klassen "T".
AA<T> abstrakt machen, hmm, dann sollte f aber f(AA<T>*v) sein. f(AA<T>&v) stellt bei mir wieder Nackenhaare auf. Da schießst du die früher oder später in den Fuß.
 
  • Gefällt mir
Reaktionen: Peter_2498
Hancock schrieb:
Die Signatur von f ist?
Die zweite Version, die du genannt hast. Läuft bis jetzt alles einwandfrei, aber jetzt wo ich mich nochmal eine deine Empfehlung erinnere sollte wahrscheinlich noch ein "const" davor.

C++:
return_type f(const AA<T>$aa);
 
Hancock schrieb:
AA<T> abstrakt machen, hmm, dann sollte f aber f(AA<T>*v) sein. f(AA<T>&v) stellt bei mir wieder Nackenhaare auf. Da schießst du die früher oder später in den Fuß.
Warum?
Die Referenz hat eine semantische Bedeutung, siehe
A pointer (T*) can be a nullptr and a reference (T&) cannot, there is no valid “null reference”. Sometimes having nullptr as an alternative to indicated “no object” is useful, but if it is not, a reference is notationally simpler and might yield better code.
https://isocpp.github.io/CppCoreGui...r-t-over-t-when-no-argument-is-a-valid-option

Finde es daher besser eine Referenz zu nutzen, wenn möglich.
 
Zurück
Oben