2020年1月17日 星期五

c++ vector 呼叫容器元素的解構函式

constructor 我喜歡翻譯成建構函式, 因為它本質的確是一個函式; destructor 我喜歡的翻譯是解構函式。當我寫 c 的時候, init() 我會取名 ctor(); destory() 我會取名為 dtor()。

在使用 std::vector 的時候, 會因為 vector 的容量變化而呼叫元素的解構函式, 這個負擔大不大呢?

vector 提供的 reserve 有沒有幫助?

我之前沒有細想這個問題。

v.push_back.cpp
 1 #include <vector>
 2 #include <stdio.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 class MyClass
 8 {
 9   public:
10     MyClass()
11     {
12       index_ = num_;
13       ++num_;
14       printf("ctor index_: %d, num_: %d\n", index_, num_);
15     }
16     MyClass(const MyClass &&rhs)
17     {
18       index_ = rhs.index_;
19       printf("move ctor index_: %d, num_: %d\n", index_, num_);
20     }
21     MyClass(const MyClass &rhs)
22     {
23       index_ = rhs.index_;
24       ++num_;
25       printf("copy ctor index_: %d,num_: %d\n", index_, num_);
26     }
27     ~MyClass()
28     {
29       --num_;
30       printf("dtor, index_: %d, num_: %d\n", index_, num_);
31     }
32     static int num_;
33     int index() const {return index_;}
34   private:
35     int  index_;
36 };
37 
38 int MyClass::num_=0;
39 
40 int main(int argc, char *argv[])
41 {
42   {
43     vector<MyClass> my_class;
44     //my_class.reserve(100);
45     cout << "capacity(): " << my_class.capacity() << endl;
46     MyClass c1,c2;
47     cout << "push c1" << endl;
48     my_class.push_back(c1);
49 
50     cout << "my_class[0].index(): " << my_class[0].index() << endl;
51 
52     cout << "c1 capacity(): " << my_class.capacity() << endl;
53 
54     cout << "push c2" << endl;
55     my_class.push_back(c2);
56 
57     cout << "my_class[1].index(): " << my_class[1].index() << endl;
58 
67   }
68   printf("end\n");
69   return 0;
70 }

由於我沒有執行 reserve, capacity() 一開始是 0, push c1 之後, vector 先配置 1 個元素的記憶體空間, copy ctor 發動, 複製 c1 到 vector[0], 目前總共有 c1, c2, vector[0] 總共 3 個 MyClass, capacity() 為 1。

push c2 之後, capacity() 空間不夠, 先配置 2 個元素的記憶體空間, 呼叫 copy ctor 複製 c2 以及在原本的 vector[0], 這時候共有 5 個 MyClass。

再來發動「原本的 vector[0]」 dtor, 現在的 MyClass 變為 4 個, 原本的有 1 個元素的記憶體空間被歸還。

在 vector 解構後, 會執行 4 次 MyClass dtor。

result 1. 總開銷
ctor: 2
copy ctor: 3
dtor: 5

嚴格來說最開始的 ctor:2, 和最後的 dtor:2 不能算在 vector 頭上, 重新計算之後。

result 2. 總開銷
ctor: 0
copy ctor: 3
dtor: 3

以 result 1 來看, 老實說我有點驚訝這個頻繁的次數, 以 result 2 結果來看就還好。

但如果用 v-1.push_back.cpp 的寫法, 因為 push_back 無法寫 my_class.push_back(); 得寫成 my_class.push_back(MyClass{});, 這寫法所有的開銷就得算在 vector 頭上了。

總開銷:
ctor: 2
copy ctor: 3
dtor: 5

和 result 1 一樣。

v-1.push_back.cpp
 1 #include <vector>
 2 #include <stdio.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 class MyClass
 8 {
 9   public:
10     MyClass()
11     {
12       index_ = num_;
13       ++num_;
14       printf("ctor index_: %d, num_: %d\n", index_, num_);
15     }
23     MyClass(const MyClass &rhs)
24     {
25       index_ = rhs.index_;
26       ++num_;
27       printf("copy ctor index_: %d, num_: %d\n", index_, num_);
28     }
29     ~MyClass()
30     {
31       --num_;
32       printf("dtor, index_: %d, num_: %d\n", index_, num_);
33     }
34     static int num_;
35     int index() const {return index_;}
36     int  index_;
37   private:
38 };
39 
40 int MyClass::num_=0;
41 
42 int main(int argc, char *argv[])
43 {
44   {
45     vector<MyClass> my_class;
46     //my_class.reserve(100);
47     cout << "capacity(): " << my_class.capacity() << endl;
48     //MyClass c1,c2,c3;
49     cout << "push c1" << endl;
50     my_class.push_back(MyClass{});
51 
52     cout << "my_class[0].index(): " << my_class[0].index() << endl;
53 
54     my_class[0].index_ = 99;
55 
56     cout << "xx my_class[0].index(): " << my_class[0].index() << endl;
57 
58     cout << "c1 capacity(): " << my_class.capacity() << endl;
59 
60     cout << "push c2" << endl;
61     my_class.push_back(MyClass{});
62 
63     cout << "my_class[1].index(): " << my_class[1].index() << endl;
73   }
74   printf("end\n");
75   return 0;
76 }

list 3. v-1 執行結果
 1 capacity(): 0
 2 push c1
 3 ctor index_: 0, num_: 1
 4 copy ctor index_: 0, num_: 2
 5 dtor, index_: 0, num_: 1
 6 my_class[0].index(): 0
 7 xx my_class[0].index(): 99
 8 c1 capacity(): 1
 9 push c2
10 ctor index_: 1, num_: 2
11 copy ctor index_: 1, num_: 3
12 copy ctor index_: 99, num_: 4
13 dtor, index_: 99, num_: 3
14 dtor, index_: 1, num_: 2
15 my_class[1].index(): 1
16 dtor, index_: 99, num_: 1
17 dtor, index_: 1, num_: 0
18 end

list 1 push_back 執行結果
 1 capacity(): 0
 2 ctor index_: 0, num_: 1
 3 ctor index_: 1, num_: 2
 5 push c1
 6 copy ctor index_: 0,num_: 3
 7 my_class[0].index(): 0
 8 c1 capacity(): 1
 9 push c2
10 copy ctor index_: 1,num_: 4
11 copy ctor index_: 0,num_: 5
12 dtor, index_: 0, num_: 4
13 my_class[1].index(): 1
15 dtor, index_: 1, num_: 3
16 dtor, index_: 0, num_: 2
17 dtor, index_: 0, num_: 1
18 dtor, index_: 1, num_: 0
19 end

如果不想付出這麼頻繁的代價, 可以使用 vector<MyClass*> my_class; 指標的版本, 或是使用 reserve, reserve 可以減少發動 ctor, dtor 的次數。

c++11 之後有了 emplace_back(), 來看看這個新東西所帶來的效率改善。

直接寫 my_class.emplace_back() 就可在 vector[0] 插入一個 MyClass 物件。

push c1 時, 發動一次 ctor, push c2 時, c2 發動一次 ctor, 原本的 vector[0] 發動一次 copy ctor, 這時候總共有 3 個 MyClass, 之後 vector[0] 發動一次 dtor, 現在的 MyClass 總數為 2 個。

當 vector 解構之後, 發動 2 次 dtor。

總開銷:
ctor: 2
copy ctor : 1
dtor: 3

和 result 2. 的開銷一樣, 不過使用 emplace_back() 可是貨真價實的省下 2 個 ctor, 這使用了 variadic template 的技術, 我總算找到使用 variadic template 的原因了。

v.emplace_back.cpp
 1 #include <vector>
 2 #include <stdio.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 class MyClass
 8 {
 9   public:
10     MyClass()
11     {
12       index_ = num_;
13       ++num_;
14       printf("ctor index_: %d, num_: %d\n", index_, num_);
15     }
16     MyClass(const MyClass &&rhs)
17     {
18       index_ = rhs.index_;
19       printf("move ctor index_: %d, num_: %d\n", index_, num_);
20     }
21     MyClass(const MyClass &rhs)
22     {
23       index_ = rhs.index_;
24       ++num_;
25       printf("copy ctor index_: %d,num_: %d\n", index_, num_);
26     }
27     ~MyClass()
28     {
29       --num_;
30       printf("dtor, index_: %d, num_: %d\n", index_, num_);
31     }
32     static int num_;
33     int index() const {return index_;}
34   private:
35     int  index_;
36 };
37 
38 int MyClass::num_=0;
39 
40 int main(int argc, char *argv[])
41 {
42   {
43     vector<MyClass> my_class;
45     cout << "capacity(): " << my_class.capacity() << endl;
48     cout << "push c1" << endl;
51     my_class.emplace_back();
52 
53     cout << "my_class[0].index(): " << my_class[0].index() << endl;
54 
55     cout << "c1 capacity(): " << my_class.capacity() << endl;
56 
57     cout << "push c2" << endl;
59     my_class.emplace_back();
60 
61     cout << "my_class[1].index(): " << my_class[1].index() << endl;
62 
71   }
72   printf("end\n");
73   return 0;
74 }

編譯: g++ -std=c++2a v.emplace_back.cpp -o v.emplace_back

list 2. emplace_back
 1 capacity(): 0
 2 push c1
 3 ctor index_: 0, num_: 1
 4 my_class[0].index(): 0
 5 c1 capacity(): 1
 6 push c2
 7 ctor index_: 1, num_: 2
 8 copy ctor index_: 0,num_: 3
 9 dtor, index_: 0, num_: 2
10 my_class[1].index(): 1
11 dtor, index_: 0, num_: 1
12 dtor, index_: 1, num_: 0
13 end

emplace_back:

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。