# 物件導向分析與設計(OOAD) ###### tags: `courses-2022-spring` ## class 1 ### lab 13 :::info In C++, you can still use pass-by-value to pass an object to function, such as: ```cpp void foo(X a) { …… } main() { X b; foo(b); m = (Y)b; } ``` Please explain 1. how C++ compiler implement the pass-by-value for you. 2. why it is not recommended to use pass-by-value to pass a object. ::: 實驗環境: ``` ❯ g++ --version g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ❯ gdb --version GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2 Copyright (C) 2020 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. ``` code: ```c= // lab13.cpp #include <stdio.h> class X { public: int a, b; X() : a(1), b(2) { printf("X's constructor\n"); } X(const X& other) : a(other.a + 1), b(other.b + 1) { printf("X's copy constructor\n"); } }; void foo(X x2) { printf("a: %d, b: %d\n", x2.a, x2.b); return; } int main() { X x1; foo(x1); return 0; } ``` 執行結果 ``` ❯ g++ -g ./lab13.cpp -o lab13.out && ./lab13.out X's constructor X's copy constructor a: 2, b: 3 ``` 根據執行結果可以觀察出 `main()` 與 `foo(X)` 之間的 pass-by-value 確實是由 copy-constructor 來達成。 用 `objdump` 觀察組語 ```asm > objdump -d -M intel ./lab13.out > lab13.asm # 擷取 main 底下的部份code ... 11d8: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 11dc: 31 c0 xor eax,eax 11de: 48 8d 45 e8 lea rax,[rbp-0x18] 11e2: 48 89 c7 mov rdi,rax 11e5: e8 4e 00 00 00 call 1238 <_ZN1XC1Ev> 11ea: 48 8d 55 e8 lea rdx,[rbp-0x18] 11ee: 48 8d 45 f0 lea rax,[rbp-0x10] 11f2: 48 89 d6 mov rsi,rdx 11f5: 48 89 c7 mov rdi,rax 11f8: e8 6f 00 00 00 call 126c <_ZN1XC1ERKS_> 11fd: 48 8d 45 f0 lea rax,[rbp-0x10] 1201: 48 89 c7 mov rdi,rax 1204: e8 80 ff ff ff call 1189 <_Z3foo1X> ... ``` `11e5` 行的 `<_ZN1XC1Ev>` 為 X constructor `X::X()` `11f8` 行的 `<_ZN1XC1ERKS_>` 為 X copy constructor `X::X(const X&)` `1204` 行的 `<_Z3foo1X>` 為 `void foo(X)` 可以發現,在組語中 `x1` 記憶體位置配置在 `rbp-0x18` 並在 `11f8` 行 呼叫 X copy constructor,用 `x1` 的資料初始化位於 `rbp-0x10` 的 class X object。 最後在呼叫 `void foo(X)` 時,將 `rbp-0x10` 的地址當作參數傳入。 使用 gdb 觀察執行過程: > Note: this is a part of screen text from gdb with gef extention ```asm $rdi : 0x00007fffffffe030 → 0x0000000300000002 ... 0x00007fffffffe018│+0x0008: 0x00000001555552f0 0x00007fffffffe020│+0x0010: 0x0000000000000000 0x00007fffffffe028│+0x0018: 0x0000000200000001 0x00007fffffffe030│+0x0020: 0x0000000300000002 ← $rax, $rdi 0x00007fffffffe038│+0x0028: 0x99c7933f0eb14000 0x00007fffffffe040│+0x0030: 0x0000000000000000 ← $rbp 0x00007fffffffe048│+0x0038: 0x00007ffff7de20b3 → <__libc_start_main+243> mov edi, eax ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ──── 0x5555555551f8 <main+60> call 0x55555555526c <X::X(X const&)> 0x5555555551fd <main+65> lea rax, [rbp-0x10] 0x555555555201 <main+69> mov rdi, rax → 0x555555555204 <main+72> call 0x555555555189 <foo(X)> ``` 可以注意到位置 `0x00007fffffffe028` 便是變數 `x1` 所在的位置, 而位置 `0x00007fffffffe030` 的 `0x0000000300000002` 便是複製後的 Class X object 當作 `foo(X)` 參數得 `$rdi` 則是指向 `0x00007fffffffe030` 觀察 `foo(X)` function 部份 asm code ```asm 0000000000001189 <_Z3foo1X>: 1189: f3 0f 1e fa endbr64 118d: 55 push rbp 118e: 48 89 e5 mov rbp,rsp 1191: 48 83 ec 10 sub rsp,0x10 1195: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi ``` 可以注意到傳入的參數 `$rdi` 在完成 callee convention 後被放入 `rbp-0x8`,也就是說在 `foo(X)` 的 stack frame 之中。 :::success #### lab 13 回答 - 1-1.) C++ class 在 function 間的 pass-by-value 會在 caller 的 stack frame 中新增一個暫時性變數,並將此變數的記憶體位置作為參數傳入 calling function - 1-2.) code: `Y m = (Y)x;` 中的強治轉型會 call `Y::Y(const X&)` 的 copy constructor - 2,) 不建議使用 pass-by-value 的原因應該是因為此舉會比起直接 pass-by-address 或 pass-by-reference 的方法多出 call copy constructor 的步驟,除非真的有複製 object 的需求,否則沒有必要使用 pass-by-value ::: ### lab 14 :::info 當你在撰寫 assign operator 時,其格式上大致如下 (以 String 型別為例) ```c String& String::operator=(const String& rhs) { … return *this ; } ``` 請問說明為何這個 method 要回傳自己? ::: :::success #### lab 14 回答 因為如果回傳的不是發起 assign operator 的 object 本身,而是其他 object(`rhs` 或其他再 `new` 出來的)。在執行完 assign operator method 後 object 指向的記憶體位置就會被修改,原本的記憶體空間便會被遺忘,如果此記憶體位置是在 stack 上到沒問題,但是如果是在 heap 上,便會造成 memory leak ::: ## class 2 觀察此 code 與執行結果 ```cpp= #include <stdio.h> class Pet; class Cat; class Pet { public: int color; Pet() : color(0) { printf("Pet constructor\n"); } Pet(int c) : color(c) { printf("Pet constructor\n"); } Pet(const Cat& c) { printf("Cat to Pet copy constructor\n"); } ~Pet() { printf("Pet destructor\n"); } void speak() { printf("Pet speak\n"); } }; class Cat : public Pet { public: Cat() : Pet(1) { printf("Cat constructor\n"); } ~Cat() { printf("Cat destructor\n"); } void speak() { printf("Cat speak, nyan\n"); } }; int main(int argc, char **argv) { Cat nyanCat; nyanCat.speak(); ((Pet) nyanCat).speak(); return 0; } ``` ``` Pet constructor Cat constructor Cat speak, nyan Cat to Pet copy constructor # A copy constructor has been called Pet speak # just before the type casting Pet destructor Cat destructor Pet destructor ``` 在第 34 行的 `((Pet) nyanCat).speak()` 會先使用 copy constructor `Pet:Pet(const cat&)` 建立一個 `Pet` object,再以此 object 為參數呼叫 `Pet::speak()` 觀察組語 ```asm= # objdump -d -M intel ./a.out | less # part of asm code mov QWORD PTR [rbp-0x18],rax xor eax,eax lea rax,[rbp-0x20] mov rdi,rax call 12e2 <_ZN3CatC1Ev> ; use cat constructor to init rbp-0x20 lea rax,[rbp-0x20] mov rdi,rax call 1364 <_ZN3Cat5speakEv> lea rdx,[rbp-0x20] lea rax,[rbp-0x1c] mov rsi,rdx ; rsi -> the first variable (nyancat) mov rdi,rax ; rdi -> the temp variable ; (rbp-0x1c didnt exist in the CPP code. let's call it `tmp` for now) ; invoke `Pet::Pet(const Cat&)` with parameter: `nyancat` and `tmp` call 127e <_ZN3PetC1ERK3Cat> lea rax,[rbp-0x1c] mov rdi,rax ; rdi -> tmp call 12c2 <_ZN3Pet5speakEv> ; invoke `Pet::speak()` with variable tmp lea rax,[rbp-0x1c] mov rdi,rax call 12a2 <_ZN3PetD1Ev> ; delete tmp with `Pet::~Pet()` mov ebx,0x0 lea rax,[rbp-0x20] mov rdi,rax call 1338 <_ZN3CatD1Ev> ; delete nyancat with `Cat::~Cat()` ``` ### 上課筆記 :::warning > constructor 只需要做記憶體 init 和 allocate 就好, > 不要做額外的事情 > [name=教授] ::: ### Chapt 7 :::info Example: > S#: > 零件供應商的編號(Supplier no) > SNAME: > 零件供應商的姓名(supplier name) > CITY1 > 零件供應商的城市(The city of a supplier) > P# 零件編號 (part no.) > PNAME > 零件名稱 (part name) > COLOR 零件色彩 (part color) > WEIGHT 零件重量 (part weight) > CITY2 零件所儲存的城市 (city where the parts are stored) > QTY 零件的存量 (The quantity of the parts) ::: my ans > Supplier's parts supplier no| part no > Supplier supplier no | supplier name | supplier city > Parts part no | part name | color | weight part no | city part no | QTY ## chapt 9 當一個名詞要真的能夠成為物件,他必須要有什麼基本條件? 具有 unique id ## chapt 10 Q: Modle that is rigid unit test? ![](https://i.imgur.com/BpjnQEq.png) ## chap 11 ![](https://i.imgur.com/k9EqN0P.png) ## UML title UseCase A ```sequence User->Button (create/useCase): clickAction Button (create/useCase)->EditorBehaviorAgent: setBehavior(button.behavior) User->Canvas: clickAction Canvas->EditorBehaviorAgent: canvasMouseAction EditorBehaviorAgent->concreteEditorBehavior: canvasClick concreteEditorBehavior->Canvas: addObj ``` title UseCase B.1 ![](https://i.imgur.com/pLxECgG.png) actor User ```sequence User->button(associationLine): click() button(associationLine)->editorAgent: setBehavior(associationLineBehavior) User->Canvas: press(x,y) Canvas->+basicObj: *isInShape(x,y) basicObj-->-Canvas: objClicked alt objClicked != null Canvas->editorAgent: objMouseAction(objClicked, event) editorAgent->concreteBehavior(associationLine): objPress(objClicked, event) concreteBehavior(associationLine)->+basicObj: getPort(event.point) basicObj-->-concreteBehavior(associationLine): port else Canvas->editorAgent: canvasMouseAction(canvas, event) editorAgent->concreteBehavior(associationLine): canvasPress(canvas, event) note right of concreteBehavior(associationLine): do nothing end User->Canvas: relese(x,y) Canvas->+basicObj: *isInShape(x,y) basicObj-->-Canvas: objClicked alt objClicked != null Canvas->editorAgent: objMouseAction(objClicked, event) editorAgent->concreteBehavior(associationLine): objRelese(objClicked, event) concreteBehavior(associationLine)->basicObj: getPort(event.point) basicObj-->-concreteBehavior(associationLine): port concreteBehavior(associationLine)->a associationLine: New a associationLine->port: connect else Canvas->editorAgent: canvasMouseAction(canvas, event) editorAgent->concreteBehavior(associationLine): canvasRelese(canvas, event) note right of concreteBehavior(associationLine): do nothing end ``` ## chap 12- uml class diagram 問題: Shape 與 basicObj canvas 擁有 shape 那麼在後續處理像是移動物件等程式碼里,不就需要 down scaleing 讓 canvas.shapeList[x] 轉成 basicObj 做動作 所以在這種形況下 down scaleing is acceptable ? control class