C++ 복사생성자, Friend, const, static(저장용,메모용, 윤성우 열혈 C++ 프로그래밍 정리 CH 5,6)

RisingJade의 개발기록·2022년 2월 16일
1

Chapter 05. 복사 생성자(Copy Constructor)


05-1. '복사 생성자'와의 첫 만남

  • C++ 스타일의 초기화

    • int num = 20, int &ref=num이 둘은 아래와 같이 선언 및 초기화 가능하다.
    • int num(20); int &ref(num); 이 둘은 위와 결과적으로 동일하다.
    • 객체의 생성 또한 위와 같이 SimpleClass sim1(15, 20); SimpleClass sim2(sim1);도 가능하고 이때 두 객체간의 멤버 대 멤버 복사가 일어난다.(sim1-> sim2로 복사!)
  • 자동으로 삽입이 되는 복사 생성자

    "복사 생성자에서 매개변수는 반드시 참조형이여야 한다."
    const 선언은 필수가 아니지만 참조형 선언을 의미하는 &는 반드시 삽입해야 한다. 그이유는 나중에 알린다.

    "복사 생성자를 정의하지 않는면, 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입된다"

    class SoSimple{
    	private:
      	       int num1;
               int num2;
        public:
               SoSimple(int n1, int n2) : num(n1), num(n2){}
               SoSimple(const SoSimple &copy) : num1(copy.num1), num2(copy.num2)//이 부분이 복사 생성자이며, 
                                                                     //따로 정의하지 않아도 자동으로 생성된다.
    }
    
    SoSimple sim1 = sim2// 이 구문은 자동으로 아래와 같이 묵시적 변환이 발생한다.
    SoSimple sim1(sim2)// 이와 같은 형태로 묵시적 변환이 일어나서 복사 생성자가 호출된다.
  • 변환에 의한 초기화! 키워드 explicit으로 막을 수 있다!.

    • 묵시적 변환을 발생시키고 싶지 않다면 다음과 같이 설정하면 된다.
    explicit SoSimple(const SoSimple &copy) : num1(copy.num1), num2(copy.num2)
    {
    	//empty!
    }
    • 위의 코드와 같이 복사생성자를 만들고 explicit을 붙이면 대입 연산자 =를 통해 객체의 생성 및 초기화가 불가능해진다.
    • 이와 같은 키워드(explicit)은 묵시적 변환이 많이 발생하는 코드의 명확함을 더하기 위해 자주 사용되는 키워드로, 꼭 묵시적 변환이 좋은것만은 아닌것을 알 수 있다.
    • 추가적으로 전달인자가 하나인 생성자가 있다면, 이 역시 묵시적 변화이 발생한다.
      class SoSimple{
    private:
    	  int num1;       
     public:
     	  SoSimple(int n1) : num(n1){}
          
    };
    class SoSimple test = 3 // SoSimple test(3)으로 묵시적 변환을 해준다.
    • 이 또한 생성자 앞에 explicit키워드를 붙임으로써 해결 할 수 있다.

05-2. '깊은 복사'와 '얕은 복사'

  • 디폴트 복사 생성자의 문제점

class SimpleClass {
private:
	int num1;
	char * name;
public:
	SimpleClass(const char *myname, int n2) {
		int len = strlen(myname) + 1;
		name = new char(len);
		strcpy(name, myname);
		num1 = n2;
		
	}
	int GetNum1() const {
		return num1;
	}
	void showData() const {
		cout << name << ' ' << num1 << endl;
	}
	SimpleClass& adder(int n) {
		num1++;
		return *this;
	}
	~SimpleClass() {
		cout << "delete simpleclass" << endl;
		delete []name;		//할당 해제
	}
};

int main(int argc, char** argv)
{
	SimpleClass man1("myname", 29);
	SimpleClass man2 = man1;
	man1.showData();
	man2.showData();
	return 0;// 
}
_____________
console:
myname 29
myname 29
delete simpleclass
@@@RUNTIME ERROR@@@

위와 같이 디폴트 복사 생성자를 쓰면 디폴트 복사 생성자에서 name(copy.name) 부분에 문제가 생긴다.
문자열을 복사해서 새로 할당하는게 아니라 같은 주소만 가르키게 되어, man1이나 man2가 해제되면 나머지 하나의 delete[] name에서 가리키고 있는 주소가 이미 해제 되어있으므로 런타임 에러가 나게된다.

  • '깊은 복사'를 위한 복사 생성자의 정의

    • 위와 같은 경우, 아래와 같이 복사 생성자를 꼭 지정해서 만들어 주자...
SimpleClass (const SimpleClass& copy): num1(copy.num1)
{
	name = new char[strlen(copy.name)+1];
    strcpy(name, copy.name);
}

05-3. 복사 생성자의 호출시점

"복사 생성자의 호출 횟수는 프로그램의 성능과도 관계가 있기 때문에, 호출의 시기를 이해하는 것은 매우 중요하다."

  • 복사 생성자가 호출되는 시점은?

    1. 기존에 생성된 객체를 이용해서 새로운 객체를 초기화하는 경우(앞서 보인것)
    2. Call-by-value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우
    3. 객체를 반환하되, 참조형으로 반환하지 않는 경우

  • 메모리 공간의 할당과 초기화가 동시에 일어나는 상황!

    "함수가 값을 반환하면, 별도의 메모리 공간이 할당되고, 이 공간에 반환 값이 저장된다.(반환 값으로 초기화된다.)"

    • 대표적인 예를 보자면
int num1 = num2;// num1이라는 이름의 메모리 공간을 할당과 동시에 num2에 저장된 값으로 초기화시킨다.
____
int SimpleFunc(int n){...}
int main(void)
{
	int num=10;
    SimpleFunc(num);// 함수가 호출되는 순간 매개변수 n이 할당과 동시에 초기화!(int n = num)처럼 됨
}
____
int SimpleFunc(int n){...
	return n;// 반환하는 순간 메모리 공간이 할당되면서 동시에 초기화! int가 아닌 객체일때도 마찬가지!
}
int main(void)
{
	int num=10;
    cout << SimpleFunc(num) << endl;// SimpleFunc에서 반환한 n값을 별도의 메모리 공간에 할당되어서 
    								// 저장된다. 그리고 그 값을 출력한다. 객체일때도 마찬가지!
}
____
SoSimple obj2 = obj1; //이 경우도 위의 첫번째 구문과 같이 할당과 동시에 초기화도 이루어진다.
  • 할당 이후, 복사 생성자를 통한 초기화

    • 임시객체를 잘 이해하고 사용하자.
    • 임시객체는 다음 행으로 넘어가면 바로 소멸되어 버린다.
    • 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.
    • 참조자를 통한 것이 아니라 이미 할당된 객체에 복사 생성자를 통하여 임시객체를 복사 했으면, 다음 행에서 임시객체는 소멸된다.

05-4. OOP 단계별 프로젝트 03단계

  • 직접 개발해보자

Chapter 06. freind와 static 그리고 const


06-1. const와 관련해서 아직 못다한 이야기

  • const 객체와 const 객체의 특성들

    • const SoSimple sim(20)에서 보이는 것 처럼 객체를 대상으로 const 선언이 붙게 되면, 이 객체를 대상으로는 const 멤버함수만이 호출이 가능하다.
    • 객체의 const선언은 "이 객체의 데이터 변경을 허용하지 않겠다!."라는 뜻이다.
  • const와 함수 오버로딩

    • const 함수도 오버로딩이 가능하다.

      	class SimpleClass {
      private:
      	int num1;
      	char * name;
      public:
      	SimpleClass(const char *myname, int n2) {
      		int len = strlen(myname) + 1;
      		name = new char(len);
      		strcpy(name, myname);
      		num1 = n2;
      		
      	}
      	void SimpleFunc() {
      		cout << "simple func" << endl;
      	}
      
      	void SimpleFunc() const {
      		cout << "const simple func" << endl;
      	}
       ....
       }
       int main(int argc, char** argv)
      {	
      	const SimpleClass constSC("asd", 10);
      	SimpleClass SC("asdaasd", 110);
       SC.SimpleFunc(); // -> simple func 출력
       constSC.SimpleFunc();// const simple func 출력
      }
    • 위 코드를 통해 const 객체가 아니면 일반 simple func을 부르고 const 객체일땐, const simple func을 부르는 걸 알 수 있다.


06-2. 클래스와 함수에 대한 friend 선언

  • 클래스의 friend 선언

    • A클래스가 B클래스를 대상으로 friend 선언을 하면, B클래스는 A 클래스의 private 멤버에 직접 접근 가능하다.
    • 단, A 클래스도 B클래스의 private 멤버에 직접 접근이 가능 하려면, B클래스가 A 클래스를 대상으로 friend 선언을 해줘야 한다.
  • friend 선언은 언제?

    • friend 선언은 객체지향의 대명사 중 하나인 '정보 은닉'을 무너뜨리는 문법이기 때문에 조심이 사용하자
    • friend 선언이 좋은 약으로 사용되는 상황은, 이후에 연산자 오버로딩을 공부하면서 보게된다.

      "friend 선언은 지나치면 아주 위험할 수 있습니다. friend 선언은 필요한 상황에서 극히 소극적으로 사용해야 합니다."

  • 함수의 friend 선언

    • 전역함수를 대상으로도, 클래스의 멤버함수를 대상으로도 friend 선언이 가능하다.
    • friend로 선언된 함수는 자신이 선언된 클래스의 private 영역에 접근 가능하다.

06-3. C++에서의 static

  • C언어에서 이야기한 static

    • 전역변수에 선언된 static의 의미: 선언된 파일 내에서만 참조를 허용하겠다는 의미
    • 함수 내에 선언돈 static의 의미: 한번만 초기화되고, 지역변수와 달리 함수를 빠져나가도 소멸되지 않는다.
    void counter()
    {
    	static int cnt; // 아래 main이 여러번 불려도 딱 한번만 생성되고 함수가 끝나도 사라지지 않는다
      cnt++; // static은 초기화하지 않으면 0으로 초기화 되므로 main for문의 호출에 따라 0부터 ~10까지 증가한다.
      cout << cnt << endl;
    }
    
    int main(void){
    	for(int i =0; i < 10; i++){
      		counter();// 함수 호출 // cnt 0 -> 10
      }
      return 0;
    }
  • 전역변수가 필요한 상황

    • 각 클래스에서 같은 클래스끼리만 공유하고 싶은 data가 있을 때 필요하다.
  • static 멤버변수(클래스 변수)

    • static 멤버변수는 클래스 변수라고도 한다. 일반적인 멤버변수와 달리 클래스당 하나씩만 생성되기 때문이다.
    • 이로 인해 객체가 생성되던 생성 되있지 않던, 항상 class명으로 접근 가능하다.
      class SimpleClass{
      public:
      	...
      	static int simObjCnt;
         ...
      }
      int SoSimple::simObjCnt=0;// static 멤버변수의 초기화 -> 안하고 바로 갖다쓰면 0으로 초기화된거 사용
    • 객체 내부에 static 멤버변수가 있는것이 아니라 객체 외부에 있다. 다만 객체에게 멤버변수처럼 접근할 수 있는 권한을 줬을 뿐이다.
  • static 멤버변수의 또 다른 접근방법

    • 사실 static 멤버변수는 어디서든 접근이 가능한 변수이다.
    • public으로 선언되면 클래스의 이름 또는 객체의 이름을 통해서 어디서든 접근 가능하다.
    • private으로 선언되면 해당 클래스의 객체들만 접근이 가능해진다.
    • public static 멤버변수를 객체를 통해 멤버변수에 접근하듯이 접근하면 static 변수가 아닌 일반 멤버변수로 접근 하는 것처럼 보이니 지양하자.
      ex) Simple Class sim1(10); int n = sim1.simObjCnt

      "public static 멤버에 접근할 때에는 클래스의 이름을 이용해서 접근하는 것이 좋다"

  • static 멤버함수

    기본적으로 그 특성이 static 멤버변수와 동일하다.

    • 선언된 클래스의 모든 객체가 공유한다.

    • public으로 선언이 되면, 클래스의 이름을 이용해서 호출이 가능하다.

    • 객체의 멤버로 존재하는 것이 아니다.

      "static 멤버함수 내에서는 static 멤버변수와 static 멤버함수만 호출이 가능하다"

      이러한 static 멤버변수와 static 멤버함수를 통해 전역변수와 전역함수를 대체할 수 있다.

  • const static 멤버

    앞서 ch04에서 보였듯이, 클래스 내에 선언된 const 멤버변수(상수)의 초기화는 이니셜라이저를 통해야만 한다. 그러나 const static으로 선언되는 멤버변수(상수)는 다음과 같이 선언과 동시에 초기화가 가능하다.

    class CountryArea{
    public:
    	const static int RUSSIA =1707540;
       const static int CANADA = 123909;
       const static int KOREA = 12387;
       const static int CHINA = 2984719;
    };
    • 이렇듯 const static 상수는 하나의 클래스에 둘 이상 모이는 것이 보통이다.
    • 이렇게 정의된 상수에 접근하기 위해 굳이 객체를 생성할 필요는 없다. 이렇듯 클래스의 이름을 통해서 접근하는 것이 편하기도 하고, 접근하는 대상에 대한 정보를 쉽게 노출하는 방법이 되기도 한다.
    • const static 멤버변수는, 클래스가 정의될 때(객체가 생성될때가 아님!) 지정된 값이 유지되는 상수이기 때문에, 위 예제에서 보이는 바와 같은 방법으로 초기화가 가능하도록 문법으로 정의하고 있다.
  • 키워드 mutable

    mutable은 아래와 같은 성질을 가진다.

    "const 함수 내에서의 값의 변경을 예외적으로 허용한다."

    • 매우 제한적으로, 매우 예외적인 경우에 한해서 사용해야 한다!.
    • mutable의 과도한 사용은 C++에 잇어서 그 중요성을 인정받은 키워드인 const의 선언을 의미없게 만들어버린다.
profile
언제나 감사하며 살자!

0개의 댓글