Dies ist ein Fall, in dem Sie wirklich messen sollten.
Und ich freue mich auf dem Kopie Zuordnung Betreiber OP und zu sehen, Ineffizienz:
A& operator=(A const& other)
{A temp = other; std::swap(*this, temp); return *this;}
Was passiert, wenn *this
und other
hat die gleichen s
?
Es scheint mir, dass eine intelligentere Kopie Zuordnung könnte eine Reise auf den Haufen zu vermeiden, wenn s == other.s
. zu tun haben, alle wäre es ist die Kopie:
A& operator=(A const& other)
{
if (this != &other)
{
if (s != other.s)
{
delete [] p;
p = nullptr;
s = 0;
p = new int[other.s];
s = other.s;
}
std::copy(other.p, other.p + s, this->p);
}
return *this;
}
Wenn Sie nicht starke Ausnahme Sicherheit benötigen, nur grundlegende Ausnahme Sicherheit auf Kopie-Zuordnung (wie std::string
, std::vector
, etc.), dann gibt es eine mögliche Leistungsverbesserung mit den oben genannten. Wie viel? Messen.
Ich habe diese Klasse drei Möglichkeiten codiert:
Entwurf 1:
Verwenden Sie das obige Kopierzuweisungsoperator und dem Umzug Zuweisungsoperator # des OP 1.
Design 2:
Verwenden Sie das obige Kopierzuweisungsoperator und die 2 bewegen Zuweisungsoperator # der OP.
Design 3:
DeadMG Kopie Zuweisungsoperator für beide Kopie und Zuordnung bewegen.
Hier ist der Code, den ich zu Test verwendet:
#include <cstddef>
#include <algorithm>
#include <chrono>
#include <iostream>
struct A
{
std::size_t s;
int* p;
A(std::size_t s) : s(s), p(new int[s]){}
~A(){delete [] p;}
A(A const& other) : s(other.s), p(new int[other.s])
{std::copy(other.p, other.p + s, this->p);}
A(A&& other) : s(other.s), p(other.p)
{other.s = 0; other.p = nullptr;}
void swap(A& other)
{std::swap(s, other.s); std::swap(p, other.p);}
#if DESIGN != 3
A& operator=(A const& other)
{
if (this != &other)
{
if (s != other.s)
{
delete [] p;
p = nullptr;
s = 0;
p = new int[other.s];
s = other.s;
}
std::copy(other.p, other.p + s, this->p);
}
return *this;
}
#endif
#if DESIGN == 1
// Move assignment operator #1
A& operator=(A&& other)
{
swap(other);
return *this;
}
#elif DESIGN == 2
// Move assignment operator #2
A& operator=(A&& other)
{
delete [] p;
s = other.s;
p = other.p;
other.s = 0;
other.p = nullptr;
return *this;
}
#elif DESIGN == 3
A& operator=(A other)
{
swap(other);
return *this;
}
#endif
};
int main()
{
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::duration<float, std::nano> NS;
A a1(10);
A a2(10);
auto t0 = Clock::now();
a2 = a1;
auto t1 = Clock::now();
std::cout << "copy takes " << NS(t1-t0).count() << "ns\n";
t0 = Clock::now();
a2 = std::move(a1);
t1 = Clock::now();
std::cout << "move takes " << NS(t1-t0).count() << "ns\n";
}
Hier wird der Ausgang bekam ich:
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DDESIGN=1 test.cpp
$ a.out
copy takes 55ns
move takes 44ns
$ a.out
copy takes 56ns
move takes 24ns
$ a.out
copy takes 53ns
move takes 25ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DDESIGN=2 test.cpp
$ a.out
copy takes 74ns
move takes 538ns
$ a.out
copy takes 59ns
move takes 491ns
$ a.out
copy takes 61ns
move takes 510ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DDESIGN=3 test.cpp
$ a.out
copy takes 666ns
move takes 304ns
$ a.out
copy takes 603ns
move takes 446ns
$ a.out
copy takes 619ns
move takes 317ns
DESIGN 1
sieht für mich ziemlich gut.
Caveat: Wenn die Klasse Ressourcen hat, den ausgeplant „schnell“ werden müssen, wie Mutex-Sperre Eigentum oder Open-Staatseigentums Datei, der Design-2 bewegt Zuweisungsoperator von einer Korrektheit Sicht könnte besser sein. Aber wenn die Ressource einfach Speicher ist, ist es oft vorteilhaft, die Freigabe so lange wie möglich zu verzögern (wie im Anwendungsfall des OP).
Vorbehalt 2: Wenn Sie andere Anwendungsfälle kennen, die Ihnen wichtig sind, messen Sie sie. Sie könnten zu anderen Schlüssen kommen als ich hier habe.
Hinweis: Ich schätze die Leistung gegenüber "DRY". Der gesamte Code wird in einer Klasse gekapselt (struct A
). Machen Sie struct A
so gut wie Sie können. Und wenn Sie eine ausreichend hohe Qualität Job, dann Ihre Kunden von struct A
(die Sie selbst sein können) wird nicht versucht werden, "RIA" (Reinvent It Again). Ich bevorzuge es, ein wenig Code innerhalb einer Klasse zu wiederholen, anstatt die Implementierung ganzer Klassen immer wieder zu wiederholen.
Ich verstehe nicht ganz Ihre Frage. Warum benutzen Sie nicht einfach ein 'std :: unique_ptr' Mitglied (anstelle von 'int *') und haben die betreffenden Operatoren entweder automatisch generiert oder '= default'? –
Walter
@Walter: Die Frage war ein Lernexperiment und nicht etwas, was ich in der Produktion verwenden würde. Ich würde stattdessen 'std :: vector' wählen. Zum Zeitpunkt des Schreibens wurde 'default' nicht von MSVC implementiert. –
Gut genug, aber "MSVC" war nicht unter den Tags. – Walter