這是一個你應該真正測量的情況。
而且我看OP的副本賦值運算符,看到效率低下:
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:
使用上述拷貝賦值運算符和OP的舉動賦值操作符1#。
設計2:
使用上述拷貝賦值運算符和OP的舉動賦值操作符2#。
設計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移動賦值運算符可能會更好。但是,當資源只是內存時,通常有利的是儘可能延遲釋放資源(如OP的用例)。
注意事項2:如果您還有其他用例,您知道它們很重要,請測量它們。你可能得出不同的結論,比我在這裏。
注:我比「DRY」更看重表現。這裏的所有代碼將被封裝在一個類中(struct A
)。使struct A
儘可能好。如果你做的工作質量足夠高,那麼你的客戶struct A
(可能是你自己)不會被誘惑到「RIA」(再次重塑它)。我更喜歡在一個類中重複一個小代碼,而不是一遍又一遍地重複執行整個類。
我不完全明白你的問題。你爲什麼不簡單地使用'std :: unique_ptr'成員(而不是'int *'),並讓所討論的操作符是自動生成的還是'= default'? – Walter 2015-11-19 09:12:30
@Walter:問題是一個學習實驗,而不是我在生產中使用的東西。我會選擇'std :: vector'來代替。另外,在撰寫本文時,默認值並未由MSVC實現。 – 2015-11-19 10:22:14
不夠公平,但'MSVC'不在標籤中。 – Walter 2015-11-19 13:40:07