Frage zur Benutzung von new und delete bei einem Beispiel
-
versionsnummer schrieb:
SeppJ schrieb:
Weil das denkbar schlechter Code ist. Nicht abgucken!
Faustregel: Wenn new oder delete direkt im Code auftauchen, ist der Code höchstwahrscheinlich nicht gut...
... oder von jemandem, der sämtliche Sprachmittel genau kennt und weiß, in welcher Situation man welche am besten anwendet statt nur nachzuplappern, was gerade in Mode ist.
Nur wenn Schweine fliegen können. Ob ein bestimmter Code gut oder schlecht ist, hat jedenfalls nichts damit zu tun, wer ihn geschrieben hat.
Andererseits lässt dein überflüssiger Kommentar sehr wohl auf deine Disposition schliessen.
-
camper schrieb:
versionsnummer schrieb:
SeppJ schrieb:
Weil das denkbar schlechter Code ist. Nicht abgucken!
Faustregel: Wenn new oder delete direkt im Code auftauchen, ist der Code höchstwahrscheinlich nicht gut...
... oder von jemandem, der sämtliche Sprachmittel genau kennt und weiß, in welcher Situation man welche am besten anwendet statt nur nachzuplappern, was gerade in Mode ist.
Nur wenn Schweine fliegen können. Ob ein bestimmter Code gut oder schlecht ist, hat jedenfalls nichts damit zu tun, wer ihn geschrieben hat.
Andererseits lässt dein überflüssiger Kommentar sehr wohl auf deine Disposition schliessen.Lustigerweise habe ich gerade in einem Code-Vorschlag in einem anderen Thread ein noch unsäglicheres "
new std::shared_ptr<>
" verwendet,
das sich in diesem speziellen Fall durchaus nachvollziehbar rechtfertigen lässtDie Faustregel, möglichst kein
new/delete
zu verwenden hat allerdings nichts mit "Mode" zu tun, sondern führt fast immer zu simplerem und weniger
fehleranfälligem Code. Ich schliesse mich hier also dernew/delete
-ist-baba-Fraktion an, auch wenn ich einräume, dass es seltene Fälle gibt, wo sie Sinn machen können.Finnegan
-
Finnegan schrieb:
auch wenn ich einräume, dass es seltene Fälle gibt, wo sie Sinn machen können.
Welche Fälle?
-
SeppJ schrieb:
Finnegan schrieb:
auch wenn ich einräume, dass es seltene Fälle gibt, wo sie Sinn machen können.
Welche Fälle?
Beispielsweise die seltenen Fälle in denen man das Ownership eines Objektes durch ein Interface durchreichen möchte, das keine Smart Pointer versteht -
z.B. das Shared Ownership einesstd::shared_ptr
duch einvoid*
-Interface einer C-API, wo der Shared Pointer dann beispielsweise in einer Callback-Funktion
freigegeben werden soll.Oder wie in dem Beispiel im verlinkten Thread, wo der lockfree-queue nur mit trivialen Datentypen umgehen kann (also keine Smart Pointer im queue, lediglich nackte Pointer),
in diesem aber dennoch Shared Ownership verwaltet werden soll, damit sigergestellt ist dass das referenzierte Objekt so lange existiert wie noch ein Thread darauf zugreifen könnte.
Es wird sicher noch andere Beispiele geben, aber wie gesagt, das werden wohl alles Ausnahmefälle sein.Ich gebe allerdings zu, dass man auch in den oben genannten Fällen nicht zwangsweise ein
new
benötigt - einstd::make_unique<T>().release()
täte es natürlich auch -
allerdings macht das bei simplen internen Objekten, deren Konstruktor nicht wirft keinen Unterschied.Finnegan
-
Finnegan schrieb:
Beispielsweise die seltenen Fälle in denen man das Ownership eines Objektes durch ein Interface durchreichen möchte, das keine Smart Pointer versteht -
z.B. das Shared Ownership einesstd::shared_ptr
duch einvoid*
-Interface einer C-API, wo der Shared Pointer dann beispielsweise in einer Callback-Funktion
freigegeben werden soll.Uns ist ein unmodern verfasstes Interface gegeben - natürlich macht das den es verwendenden Code entsprechend unmodern. Wenn wir RAII von vornherein konsequent durchgesetzt hätten, wäre auch niemals ein
new
notwendig gewesen. Genau wieconst_cast
in modernem C++ prinzipiell keine Daseinsberechtigung hat - nur weil bestimmte legacy APIs einfach keine const correctness haben (außer man implementiert irgend eine Methode zweimal und möchte sich Tipparbeit sparen).
Außerdem sollte nicht einfach
new
verwendet werden, weil irgendwelche Parameter rohe Zeiger sind.foo(new A, new B); // Kann lecken auto a = std::make_unique<A>(); auto b = std::make_unique<B>(); foo(a.release(), b.release()); // Leidet nicht unter dem selben Problem.
Im Übrigen sehe ich nicht ein, weshalb Faustregeln wie diese nicht dogmatisiert werden sollen. Wir hatten genug Neulinge, die sich "passt schon, funzt ja" einreden und dann mit völlig schrottem Code antanzen. Lieber einfach kategorisch ablehnen, alle Ausnahmen sind so spezialisiert, dass sie im Falle des Falles auch hier diskutiert werden können.
-
Arcoth schrieb:
Uns ist ein unmodern verfasstes Interface gegeben - natürlich macht das den es verwendenden Code entsprechend unmodern. Wenn wir RAII von vornherein konsequent durchgesetzt hätten, wäre auch niemals ein
new
notwendig gewesen.Ich bin mir da nicht so sicher. Wie implementiert man denn ohne
new
so etwas wiemake_unique
oder sogarstd::vector
wo einreserve()
zwar
den Speicher für die Objekte reserviert, diese aber erst später konstruiert werden? Ich denke zumindest ein placement new wird benötigt -
oder vielleicht die Möglichkeit den Konstruktor für ein nicht initalisiertes Objekt manuell aufzurufenArcoth schrieb:
Im Übrigen sehe ich nicht ein, weshalb Faustregeln wie diese nicht dogmatisiert werden sollen. Wir hatten genug Neulinge, die sich "passt schon, funzt ja" einreden und dann mit völlig schrottem Code antanzen. Lieber einfach kategorisch ablehnen, alle Ausnahmen sind so spezialisiert, dass sie im Falle des Falles auch hier diskutiert werden können.
Nur zur Erinnerung, ich bin nicht derjenige der sich gegen die Faustregeln auflehnt, im Gegenteil, ich hab sie sogar verteidigt (! s.o.).
Ich hatte nicht vor hier eine Diskussion vom Zaun zu brechen, bei der ich eh auf eurer Seite bin, sondern es ging mir lediglich darum auf ein Beispiel zu verweisen,
wo ichs zufällig gerade eben erst verwendet hatte. Dort wird vianew
lediglich der (noexcept
) Copy Constructor vonshared_ptr
in einer privaten Methode aufgerufen,
was man meines erachtens durchas auch mal mitnew
machen darf, wenn der Code dadurch kompakter wird. Nicht jeder Code ist so generisch dass man mit allem rechnen mussFinnegan
-
hi,
ich möchte mich hiermit bei allen für die Hilfe bedanken.
-
Arcoth schrieb:
[code="cpp"]
foo(new A, new B); // Kann leckenauto a = std::make_unique<A>();
auto b = std::make_unique<B>();
foo(a.release(), b.release()); // Leidet nicht unter dem selben Problem.[/codeWo werden denn hier die Ressourcen freigegeben?
[code="cpp"]class A
{
public:
A::A(){cout << "Constructor\n";}
A::~A(){cout << "Destructor\n";}
};void foo(A *ptr)
{
cout << "Foo\n";
}int main()
{
foo(make_unique<A>().release());
}[/code]
erzeugt bei mir die Ausgabe:
Constructor
Foo
-
Finnegan schrieb:
Arcoth schrieb:
Uns ist ein unmodern verfasstes Interface gegeben - natürlich macht das den es verwendenden Code entsprechend unmodern. Wenn wir RAII von vornherein konsequent durchgesetzt hätten, wäre auch niemals ein
new
notwendig gewesen.Ich bin mir da nicht so sicher. Wie implementiert man denn ohne
new
so etwas wiemake_unique
oder sogarstd::vector
wo einreserve()
zwar
den Speicher für die Objekte reserviert, diese aber erst später konstruiert werden? Ich denke zumindest ein placement new wird benötigt -
oder vielleicht die Möglichkeit den Konstruktor für ein nicht initalisiertes Objekt manuell aufzurufenDanke für den Nitpick, aber ich rede hier natürlich von Code außerhalb der Standardbibliothek.
@Belli: Die Idee war, dass
foo
Besitz der Objekte ergreift.
-
Ach soooo ...
foo(new A, new B); // Kann lecken
lecken also für den Fall, dass beim zweiten Konstruktoaufruf was schief geht?!
So weit hatte ich ursprünglich nicht gedacht ...
Denn Besitz übernehmen könnte (bzw. müsste) foo schließlich auch so?!
-
ja, und du kannst, wenn es ein funktionsaufruf ist, nicht einmal garantieren, in welcher reihenfolge A und B konstruiert werden. wenn B::B fehlschlägt und nach A::A aufgerufen wird, dann gibt es keine möglichkeit mehr, das A-objekt freizugeben; foo wird auch niemals aufgerufen.
-
Hallo Arcoth,
Arcoth schrieb:
class Mammal { public: virtual ~Mammal() =0; // pure abstract; Instanz von Mammal ist nicht möglich! virtual void Speak() const { std::cout << "???" << std::endl; } // Default }; inline Mammal::~Mammal() {}
Warum nicht
class Mammal { public: virtual ~Mammal() = default; virtual void Speak() const = 0; };
zu Speak: aus dem Beispielcode des TE ist meines Erachtens ersichtlich, dass es eine Default-Implementierung für die Methode Speak gibt. Das wollte ich dann genau so machen.
zu ~Mammal: siehe Scott Meyers 'Effektiv C++' Item 14 "eine Basisklasse soll einen virtuellen Destruktor haben" und noch ein Item (wahrscheinlich aus 'Mehr Effektiv C++'), was ich jetzt nicht wieder finde, was besagt: "Mache Dir keine Instanzen von Basisklassen". Letzeres kann ich aus eigener (schmerzlicher!) Erfahrung bestätigen.Gruß
Werner