음악, 삶, 개발

18. Vectors and Arrays 본문

개발 공부/Principles And Practice Using C++

18. Vectors and Arrays

Lee_____ 2020. 7. 25. 02:03
lee : 이 chapter는 굉장히 중요한데, 간략하게 읽어보니 설명이 너무 어렵다. 
앞 chapter 에서 사용했던 예제 코드로 계속 설명을 이어나가서, 오히려 이해가 안 되는 부분이 너무 많다.
그래서 (생략) 이 많을듯하다. 의미가 없어서가 아니라, 다른 책을 통해 배워야할거같다.
일단 요약할 수 있는 데까지는 요약해보겠음.
추후 목차에 적힌 제목들을 구글링 하자.

 

이 챕터에서는 아래의 것들을 배운다.

  • vector 가 subscripting을 통해 어떻게 copy 되는지.

  • array와 vector의 관계

  • pointer와 array의 사용을 통해 나타나는 문제점

  • 모든 type에서 필요한 5가지 필수적인 연산 : construction, default construction, copy construction, copy assignment, destruction.

  • move constructor, move assignment for container


18.1 Introduction

 

생략.


 

18.2 Initialization

 

생략.


18.3 Copying

 

우리가 직접 vector라는 class를 만든다고 가정해보자.

class vector {

    public :

        vector(int s) : size {s}, element{new double[s]} {} // constructor
        ~vector() { delete[] element; } // destructor

    private :

        int size;
        double* element; 

};

우리가 만든 vector class의 2개의 객체를 만들어, copy를 시도해보자.

vector v(3);
v.set(2, 2, 2);
vector v2 = v; // copy 할려는 시도. 어떻게 될까?

class 안에서 copy 의미는  "Copy all data members"이다.

std::vector는 복사를 시도할때, 우리가 생각하는대로 모든것이 제대로 복사되지만, 

우리가 만든 vector 는 pointer를 member로 가지고 있는데,

pointer를 복사하게되면 우리가 의도한 진짜 복사가 아니라,

특정한 객체를 가리키는 포인터가 2개가 되어버린다.

pointer 를 복사했을때 한 객체를 2개의 포인터가 가리키고있다.

한마디로, pointer 를 복사하게 되면, 객체를 공유하게 되는 것이다.

따라서 한 pointer를 통해 객체를 변경하게 되면, 다른 pointer 역시 동일한 객체를 가리키고 있기 때문에,

pointer를 통해 값을 출력하면 둘은 동일한 값을 나타낸다.

v.set(1, 99);
v2.set(0, 88);

std::cout << v.get(0) std::endl; // 88 이 출력
std::cout << v2.get(1) std::endl;  // 99가 출력

이 둘의 각 pointer는 똑같은 객체를 가리키고 있으므로 인해,

destructor 가 2번 호출되는 참사가 일어날 수 있다.


18.3.1 Copy constructors

 

우리가 정의한 class의 객체 간에 복사를 제대로 하기 위해서

우리는 copy 연산을 제공해야 하고, 이 copy 연산이 

객체를 다른 객체에 의해 초기화하려 할 때 call 되도록 해야 한다.

class의 객체의 초기화는 constructor에 의해 실행된다.

copy를 위해 사용하는 constructor를 copy constructor라고 한다.

copy constructor는 우리가 copy 하려는 객체를 reference로 argument로 넘김으로써 정의할 수 있다.

vector(const vector&);

위의 constructor는, vector를 다른 vector로 초기화하려 할 때 호출될 것이다.

이때 pass-by-const-reference 인 이유는 우리는 argument를 복사하고 싶지 않고,

또 우리는 우리의 argument를 modify 하고 싶지 않기 때문이다.

우리가 만들고 있는 vector class를 다시 정의해보자.

class vector {

    public :
        
        // copy constructor
        vector(const vector& arg) 
            
            : size {arg.size}, element{new double[arg.size]}
        
        {

            std::copy(arg, arg + size, element); // 나중에 배움.

        }

    private :

        int size;
        double* element; 

};

vector v2 { v }; // vector v2 = v; 이렇게 적어도됨. 취향에 따라..

위와 같이 copy constructor를 정의하고 나면,

우리가 생각하는 복사가 가능하다. 이제 pointer는 각자의 객체를 가리킨다.

복사

이런 그림이 되어야만, destructor 또한 올바르게 작동한다.

(각 element 가 올바르게 free 된다.)

이제야 2개의 vector는 서로 아무것도 공유하지 않는 독립적인 상태다.

따라서, vector의 element 값을 바꾼다 하더라도, 다른 vector element의 값에는 영향을 미치지 않는다.


18.3.2 Copy assignments

 

Copy constructor는 다른 객체를 대입하면서 초기화를 할 때 사용하기 위한 constructor이다.

Copy constructor와 Copy assignment의 사용법은 다르다. (헷갈리지 말자!)

예를 보자.

vector v { v1}; // v 를 초기화하기위해 v1 을 사용했음으로 copy constructor 호출됨.

v = v2; // copy assignment 가 호출됨.

Copy assignment 가 class에서 정의되어있지 않았을 때는,

default assignment (member copy 하기)가 사용된다. 이때의 문제점을 코드로 보도록 한다.

vector v(3);
v.set(2, 2, 2);
vector v2(4);
v2 = v;

 

위와 같이 v2에 v를 assignment 하면 결과는 아래 그림과 같다.

default assignment 가 사용되었을때.

앞서 설명한 예와 똑같은 현상이 발생한다.

pointer 가 본인의 객체를 가리키지 않고 남의 객체를 가리키고 있다.

이런 과정 속에 memory leak 이 발생한다. 우리는 free 하는 것을 잊었다.

따라서 copy constructor 가 필요한 이유와 copy assignment 가 필요한 이유는 동일하다.

copy assignment는 operator= 를 사용하여 정의한다.

class vector {

    public :

        vector& operator=(const vector& a) {

            double* p = new double[a.size];
            std::copy(a.element, a.element + a.size, element); 
            delete[] element;
            element = p;
            size = a.size;

            return *this;

        }

    private :

        int size;
        double* element;

}

copy assignment 대부분  copy constructor의 코드보다  길고 복잡하다.

왜냐면 기존의 element 들에 대해 어떻게 처리해야 할지 logic을 짜야하기 때문이다.

copy assignment 가 호출된 후의 그림을 보면 아래와 같다.

 

copy assignment 호출후.

copy assignment를 정의했을 때, memory leak과 free 가 2번 일어나는 것을 방지할 수 있다.


18.3.3 Copy terminology

 

복사는 대부분의 프로그램 언어와 프로그램 안에서 늘 문제이다.

기본적인 문제는, 당신이 pointer 또는 reference를 copy 할 것인지,

또는 pointer 가 가리키는 객체의 정보를 copy 할 것인가이다.

이때 shallow copy (얕은 복사)와 deep copy (깊은 복사)의 차이에 대해 알아야 한다.

  • Shallow copy (얕은 복사) : pointer 만 복사하여, 2개의 pointer 가 똑같은 객체를 가리킨다.

  • Deep copy (깊은 복사) : 2개의 pointer는 2개의 서로 다른 객체를 가리킨다. (vector, string 이 이렇게 작동함)

deep copy를 하기 위해서는 copy constructor와 copy assignment를 직접 정의해줘야 한다.

이 둘의 차이를 코드와 그림을 표현해보자.

 

Shallow Copy

int* p = new int { 77 };
int* q = p; // pointer 를 복사함
*p = 88; // p 와 q 는 같은 객체를 가리키기에 *p, *q 둘다 바뀜. 

Shallow copy (얕은 복사)

Deep copy

int* p = new int {77};
int* q = new int{*p}; // 새로운 memory 를 할당하고 *p 로 값만 복사.
*p = 88; // p 가 가리키는 객체만 바뀜.

우리가 앞서 정의했던 vector의 문제점은 shallow copy 였던 것이다.

(std::vector와 std::string 은 memory를 새로 할당하는 deep copy를 한다)

shallow copy 를 제공하는 pointer 나 reference 같은 type 들은,

pointer semantic 또는 reference semantic을 가지고 있다고 이야기한다. (값 대신 주소를 복사하는..)

이와 반대로 deep copy를 제공하는 type 들은 value semantic을 가지고 있다고 말한다.(값이 복사되니까)


18.3.4 Moving (Move constructor, Move assignment) (어려워서 나중에 요약할 것!)

 

vector 가 매우 많은 양의 element를 가지고 있다면 copy 하는 것은 매우 expenssive 하다.

음... 예제를 봐도 이해가 되지 않는다.

너무 어렵다. 

&& 연산자가 등장한다.

봐도 모르겠다. 추후에 검색하는 걸로.. (반드시 알아야 한다.)

일단 패스. 

나중에 다시 요약하러 돌아오겠음.

Peter에게 && 를 자주 쓰냐고 물어봤음.

대답.


18.4 Essential operation

 

아래는 우리가 class를 만들 때 고려해야 할 필수적인 7가지 연산이다.

  • Constructors from one or more arguments

  • Default constructor

  • Copy constructor (copy object of same type)

  • Copy assignment (copy object of same type)

  • Move constructor (move object of same type)

  • Move assignment (move object of same type)

  • Destructor

우리는 객체를 초기화하기 위한 argument를 받기 위해 대부분 최소 1개 혹은 그 이상의 constructor 가 필요하다.

초기화를 어떻게 할 것인지는 철저하게 constructor에 달려있다.

우리가 constructor를 사용하는 이유는, invariant(불변 속성)을 확립하기 위함이다.

(invariant 란?)

우리가 constructor에게 좋은 invariant를 정의하지 못한다면, 그 class의 design 은 매우 잘못되었다고

할 수 있으며, 그저 data structure에 지나지 않는다.

"어떻게 default constructor 가 필요한지 알 수 있을까?"

우리가 특별한 초기화 없이 객체를 생성하고자 한다면 default constructor 가 필요하다.

코드를 보자.

vector<double> vi(10); // 각 element 는 0.0 으로 초기화됨.
vector<string> vs(10); // 각 element 는 "" 으로 초기화됨.
vector<vector<int>> vvi(10); // 각 element 는 vector {} 로 초기화됨.

위와 같은 코드 (초기하 하지 않은)를 작성할 수 있는 이유는,

vector 가 내부적으로 default constructor를 가지고 있기 때문이다.

독자가 또 다른 질문을 가져볼 수 있다.

"언제 default constructor를 가져야 하는가?"

대답은, "우리가 class를 의미 있고 확실한 default value로 invariant를 확립할 수 있을 때"이다.

 

예를 들어, int의 명확한 default 갚는 0, double 은 0.0, string 은 " ", vector는 { } (empty)이다.

모든 type 뒤에 { }를 붙이면 default 가 존재한다면 default value로 초기화된다.

double d { }; // 0.0
string { }; // " "
vector<int> v { } // empty vector of ints
언제 destructor를 가져야 하는가?

class 가 resource를 필요로 한다면 destructor 가 필요하다.

resource 란,  당신이 "get from somewhere" 하는 something이며,

사용을 끝냈다면 somewhere로 다시 돌려줘야 하는 something이다.

확실한 예는, free store에서 new를 통해 할당받는 memory이다.

우리는 dealt 또는 delete []를 통하여 free store에 사용한 메모리를 다시 반환해줘야 한다.

또 다른 예는 file이다. 우리가 파일을 open 했다면 사용 후 반드시 close 해줘야 한다.

lock, thread handle, socket 등도 이에 포함된다.

class 가 destructor 가 필요함을 알려주는 중요한 신호는, 

member로 pointer 나 reference를 가지고 있을 때이다.

class 가 pointer 나 reference member를 가지고 있다면, destructor와 copy operation 이 일반적으로 필요하다.

destructor 가 필요한 class는 거의 항상 (almost always) copy constructor와 copy assignment 가 필요하다.

왜냐면 객체가 resource를 획득하였고, pointer 멤버가 이 resource 를 가리키고 있다면

copy의 default 방식 (shallow copy)는 확실히 잘못되었기 때문이다.

유사하게, destructor를 필요로 하는 class는 move constructor와 move assignment 가 필요하다.

이유는, resource를 획득한 객체에게 copy operation 은 매우 expensive 하기 때문이다.

추가적으로  base class는 일반적으로 virtual destructor (17.5.2)가 필요하다.


18.4.1 Explicit constructors

 

단일 argument를 받는 constructor는 자신의 argument type을 class의 type으로 바꾸는 conversion을 정의한다.

가끔은 유용하다.

class complex {
	public:
    
    complex(double);

}

complex z1 = 3.14; // OK : convert 3.14 to  {3.14, 0}

하지만 이런 암시적 형 변환의 사용은 늘 조심해야 한다. 

이런 문제를 방지하기 위해 constructor 앞에 explicit라는 키워드를 붙이면,

implicit conversion을 할 수 없게 된다.

class complex {

    explicit complex(int);

};

complex c = 10; // error : no int-to-complex conversion
c = 20; // error : no int-to-complex conversion
complex v2(10); // OK

void f(const complex&);
f(10); // error : no int-to-complex conversion
f(complex(10)); // OK

std::vector의 single argument constructor는 explicit이다.

불행히도, constructor의 default는 explicit 가 아니다.

따라서, single argument를 받는 constructor를 만든다면 explicit를 붙이는 것이 좋다.


18.4.2 Debugging constructors and destructors

 

언제 constructor, destructor 가 호출되는지 (invoke) 알아야 한다.

 

constructor 가 호출될 때. ( = 객체가 생성될 때)

  • variable 이 초기화될 때.

  • bulit-in type이 아닌 객체가 new를 통해 생성될 때.

  • 객체가 copy 될 때.

destructor 가 호출될 때 ( = 객체가 파괴될 때)

  • { } scope을 넘어갔을 때

  • program 이 종료되었을 때.

  • 객체를 가리키는 pointer에 delete 가 사용되었을 때.

우리가 class를 만들었다면, 각 constructor와 destructor 안에,

std::cout 같은 함수를 사용하여, 모든 것이 의도한 대로 올바르게 invoke 되는지 console로 확인하는 것이 중요하다.

또한 constructor 나 destructor 과정에서 memory leak 이 발생하는지 확인하는 것 또한 중요하다.

class 가 memory를 할당받거나, 객체가 pointer를 쥐고 있는 경우

copy constructor 나 copy assignment를 정의하는 것을 잊어버리는 일은 매우 흔한 일이기 때문이다.

너의 문제가 너무 커서 단순한 solution으로 check 하기 어렵다면,

"leak detector" 같은 tool을 활용하는 것을 고려해봐야 한다.

물론, 가장 이상적인 것은 코드를 잘 작성하여 leak을 피해야 한다.


18.5 Access to vector elements

 

operator[] 을 통해 [] 연산자를 class 내에서 정의하는법에 대해 나온다. 이하 생략.


18.5.1 Overloading on const

double& operator [] (int n);
double operator[] (int n} const; 

모르겠다 무슨말인지. 추후 검색할것..


18.6 Arrays

 

lee : 결론은 "array 를 사용하지말고 vector만 써라"지만, 다른 사람의 code 를 보기위해 공부는 해야한다고한다. 

array 의 문제점을 알려준다.

 

array 는 객체의 sequence 를 free store 에 할당하기위해  사용한다.

array는 아래와 같이 사용될수있다.

  • global variable (하지만 global 는 bad idea 다)
  • local variable (하지만 array 는 local 에서 심각한 limitation 이 있다)
  • function argument (하지만 array 는 자신의 size 를 모른다)
  • class member (하지만 array member 는 초기화하기 힘들다)

array의 각 element 는 memory 에서 gap 없이 지속적인 구간으로 할당된다. 

중요한건 array의 element 수는 compile-time 에서 정해져야한다. (must be const expression)

따라서 vector 와 달리 아래와같은것은 불가능하다.

void f(int arrayNumber) {
	
    double myDoubleArray[arrayNumber]; // error : array size not a constant

}

아래와 같은것은 가능하다.

const int max = 100;
int arr[max];

하지만 array 의 문제점은 아래와 같다.

  • 자신의 size 를 모른다.
  • array 를 사용하는것은 memory 에 직접적으로 접근하는것이다.(system support 를 받을수없음)
  • compile-time 에 array bounds 를 추적할수없다.
  • 따라서 debug 가 매우 어렵다

18.6.1 Pointers to array elements

 

pointer 를 통해 array 의 element 를 가리킬수있다.

double ad[10];
double* p = &ad[5]; // 5번째 element 를 가리키는 pointer 를 만들었다

그림으로 나타내면..

array 의 element 를 가리키는 pointer

pointer 를 통해 다른 element 를 subscript ( [ ] ) 하거나 dereference ( * ) 할수있다.

*p = 7;
p[2] = 6;
p[-3] = 9;

pointer 를 통한 subscript 와 dereference

위의 코드처럼, 우리는 pointer 를 subscript 할수있는데,

양수(+) 와 음수(-) 둘다 가능하다.

음수여도, element 가 범위안에 있는한 값을 받을수있다.

하지만, array 의 범위 바깥을 pointer 하는것은 illegal 임에도 compiler 에 detect 되지않는다.

pointer 에 int 를 더하거나 subscript 을 통해  다른 element 를 가리키게 할수있다.

p += 2; // 오른쪽으로 2칸 움직여라.

+ 2 를 해서 오른쪽 2칸에 있는 element 를 가리킴.

p -= 5; // 왼쪽으로 5칸가서있는 element 를 가리켜라.

왼쪽으로 5칸 이동해서 (-5) 있는 element 를 가리킴.

pointer 에 +, -, +=, -= 를 사용하는것을 pointer arithmetic (포인터 연산) 이라고 부른다.

포인터를 연산을 하고자한다면, pointer 가 가리키는곳이 array 의 바깥이 아니도록 조심하여야한다.

p += 1000; // 미쳤음. 10개짜리 element 인데 1000칸 옆으로가라고?
double d= *p; // illegal! : 예측할수없는 값이 들어올것이다.
*p = 12.13 // 여전히 illegal! : 이상한 data 와 섞일것이다.

 

pointer 연산의 대부분의 사용은 아래와 같다.

  • ++p : 다음 element 를 가리킨다.
  • --p : 이전 element 를 가라킨다.

하지만 pointer 연산을 했을때 생기는 bug 는 매우 찾기어렵다.

compiler 는 pointer 가 가리키는 array 의 element 갯수가 몇개인지 모른다. (vector 는 compiler 가 range-check 할수있음)

따라서 pointer 연산을 아예 하지않는것이 최고다.

 

"그럼 왜 C++ 에서 pointer 연산이 가능하게 해놨나요??"

 

이유는, C 와의 호환성때문이다. 만약 이 기능을 제거했다면, 많은 code 들이 break 되었을것이라서.

또한 pointer 연산이 가끔 불가피한 low-level 프로그램들이 있다. ex) 메모리 매니저


18.6.2 Pointers and arrays

 

array 와 pointer 에서 confusing 한 부분이 있다.

아래에 코드와 설명을 적어놨으니 읽어보라.

char ch[100]; // ch 는 array 를 가리킨다.
sizeof(ch); // 100 을 출력한다.

/* 근데 ch 를 pointer 에 넣으면 첫번째 주소값으로 대입된다*/
char* p = ch; // ch 는 &ch[0] 과 동일하다.
sizeof(p); // 4 가 출력된다.

이렇게 array 의 이름이 첫번째 element 의 주소값으로 사용될수있도록 설계된 이유는,

실수로 거대한 array 가 인자로 넘겨지는것을 방지하기위함이었다.

또한 주의해야할것은, array 의 이름은 variable 이 아니라 value 라서,

다른 무언가를 assign 할수없다.

char ac[10];
ac = new char[20]; // error : no assignment to array name
&ac[0] = new char[20]; // error : no assignment to pointer value 

array 의 이름은 pointer 로 암시적으로 변환되기때문에, 

assignment 를 array 를 copy 하는 용도로 사용할수없다.

int x[100];
int y[100];

x= y; // error
int z[100] = y; // error

따라서, array 를 copy 하기위해서는 꽤나 정교한 code 를 따로 작성해야한다.

그냥 언제나 vector 를 쓰는게 가장 행복한 길이다.

vector 에서는 아래와 같은 copy 가 가능하다.

vector<int> x(100);
vector<int> y(100);

x =y; // copy 100 ints!

18.6.3 Array initialization

 

char type 의 element 를 가진 array 는 string 리터럴로 초기화할수있다.

char ac[] = "Beorn"; // array of 6 chars

주의할것은, 위의 글자는 총 6개로 봐야한다는것이다.

compiler 가 자동으로 string 리터럴의 제일 마지막 0 을 추가한다.

string 은 항상 0 으로 끝남.

이렇게 string 이 0으로 끝나는것은 많은 시스템에서 매우 일반적이며, C-style string 이라고 부른다.

char *pc = "Howdy";

포인터가 가리키는 string

중요한건 마지막 0 은 "0" 이란 문자가 아닌 숫자다.

이렇게 0을 마지막에 추가하는 이유는, 함수가 string 의 끝을 알수있도록하기위함이다.

std::string (string.h 파일) 에서 string 의 길이를 알려주는 strlen() 이라는 함수가 있다.

주의할점은, strlen() 이 return 하는것은 마지막 0을 제외한 순수 문자의 갯수다.

따라서 C-style string 이 만들기위해 당신은 n + 1 로 저장해야한다.

 

literal string 으로 char열을 초기화할때만 자동으로 뒤에 0 을 추가하고,

그외에 array 는 그렇지않다.

아래 코드는 array 의 초기화 특성이다.

int ai[] = {1, 2, 3, 4, 5, 6}; // [] 안을 비워나도 된다. compiler 가 알아서 함. 우리는 list 로 초기화했으므로.
int a2[100] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 마지막 90 element 는 0 으로 초기화된다 (default 값)
double ad[100] = {}; // 0.0 으로 모두 초기화된다.
char chars[] = {'a', 'b', 'c'}; // 0 으로 안끝남! 리터럴아님!

18.6.4 Pointer problems

 

pointer 를 사용할때의 문제는 array 사용의 문제점과 비슷하다.

pointer 의 사용으로 인해 생길수있는 문제는 아래와 같다.

  • null pointer 를 통해 접근하려할때.
  • 초기화되지않은 pointer 를 통해 접근하려할때.
  • array 의 end 바깥을 접근하려할때.
  • 할당 해제된 객체를 접근하려할때.
  • scope 가 끝난 객체를 접근하려할때.
int* p = nullptr;
*p = 7 // ouch!

위의 코드를 보면 당연히 우리는 문제를 알수있지만,

현실 프로그램에서는 이런 문제는 초기화와 사용 사이의 코드에서 자주 발생한다.

nullptr 를 함수의 인자로 넘기지않는것이 가장 좋지만,

이것을 확실할수없다면 아래와 같이 작성할수있다.

 

void canReceiveNullPointer(int* p) {
	
  if (p == nullptr) {
  
  	// do something

  }
  
  else {
  
  	// use p 
    
    *p = 7;
  
  }

}

 

reference 와 error 를 나타내기위한 exception 을 사용하는것이 null pointer 를 피하기위한 가장 좋은 방법이다.

언제나 loop 의 시작과 끝을 조심해야한다.

array 의 첫번째 element 를 접근하기위해 pointer 를 사용하는것을 하지말아야한다. 대신에 언제나 vector 를 사용

하라.

가장 효율적인 방어는 언제나 naked delete 을 요구하는 naked new 를 코드에서 작성하지않는것이다.

new 나 delete 를 constructor 와 destructor 안에서만 사용하던지,  vector 같은 container 를 사용하라.

 

수많은 숙력된 프로그래머들 조차도 이 array 와 pointer 가 짬뽕되어 나타내는 문제에 패배하였다.

메로리 관리와 다른 리소스들을 효율적으로 관리하기위해서는 

언제나 vector, RAII (Resource Acquisition Is Initialization) (19.5 에 나옴), 다른 시스템적인 접근을 해야한다.


18.7 Examples : palindrome

18.7.1 Palindromes using string

18.7.2 Palindromes using arrays

18.7.3 Palindromes using pointers

 

생략.


Review

 

  1. What does "Caveat emptor!" mean?
  2. What is the default meaning of copying for class objects?
  3. What is the default meaning of copying of class objects appropirate? When is it inappropirate?
  4. What is a copy constructor?
  5. What is a copy assignment?
  6. What is the difference between copy assignment and copy initialization?
  7. What is shallow copy? What is deep copy?
  8. How does the copy of a vector compare to it's source?
  9. What are the five "essential operations" for a class?
  10. What is an explicit constructor? Where would you prefer one over the default alternative?
  11. What operations may be invoked implicitly for a class object?
  12. What is an array?
  13. How do you copy an array?
  14. How do you initialize an array?
  15. When should you prefer a pointer argument over a reference argument? Why?
  16. What is a C-style string?
  17. What is a palindrome?

Terms

 

  • array
  • array initialization
  • copy assignment
  • copy constructor
  • deep copy
  • default constructor
  • essential operations
  • explicit constructor
  • move assignment
  • move construction
  • palindrome
  • shallow copy