Это случай, когда вы должны действительно измерить.
И я смотрю на оператора Ор в копию назначения и видя неэффективность:
A& operator=(A const& other)
{A temp = other; std::swap(*this, temp); return *this;}
Что делать, если *this
и other
имеют одинаковый s
?
Мне кажется, что более разумное назначение копии могло бы избежать поездки в кучу, если s == other.s
. Все это было бы сделать, это копия:
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;
}
Если вы не нужно сильной безопасности исключений, только элементарную безопасность исключения на присвоении копии (как std::string
, std::vector
и т.д.), то есть потенциальное повышение производительности с вышесказанным. Сколько? Мера.
Я закодирован этот класс тремя способами:
Дизайн 1:
Используйте выше оператор копирующего присваивания и ФП в присваивание шаг оператор # 1.
Design 2:
Используйте выше оператор копирующего присваивания и ФП в присваивании движения оператор 2 #.
Design 3:
оператор присваивания копии DeadMG для обоих копирования и перемещения назначения.
Вот код, который я использовал для теста:
#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";
}
Вот выход я получил:
$ 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
выглядит довольно хорошо для меня.
Предостережение. Если у класса есть ресурсы, которые необходимо освободить «быстро», такие как блокировка блокировки мьютекса или права собственности на файл с открытым исходным кодом, оператор присваивания перемещения проекта-2 может быть лучше с точки зрения правильности. Но когда ресурс является просто памятью, часто бывает выгодно отложить его освобождение как можно дольше (как в случае использования ОП).
Оговорка 2: Если у вас есть другие варианты использования, которые вы знаете, чтобы быть важными, измерьте их. Вы можете прийти к разным выводам, чем здесь.
Примечание: Я оцениваю производительность сверх «СУХОЙ». Весь код здесь будет инкапсулирован в один класс (struct A
). Сделайте struct A
как можно лучше. И если вы выполняете достаточно качественную работу, то ваши клиенты из struct A
(которые могут быть сами) не соблазнится «RIA» (Reinvent It Again). Я предпочитаю повторять небольшой код внутри одного класса, а не повторять реализацию целых классов снова и снова.
Я не совсем понимаю ваш вопрос. Почему бы вам просто не использовать 'std :: unique_ptr' member (вместо 'int *') и иметь заданные операторы либо автоматически сгенерированные, либо '= default'? –
Walter
@Walter: Вопрос был учебным экспериментом, а не тем, что я буду использовать в производстве. Вместо этого я бы выбрал 'std :: vector'. Кроме того, на момент написания сообщения MSVC не реализовал 'default'. –
Достаточно справедливо, но 'MSVC' не был среди тегов. – Walter