Publish:

태그: , ,

카테고리:


🤯 언리얼을 하기 위해 C++ 기억 되살리기 프로젝트

개체지향 프로그래밍 (OOP) 2

1. 복사 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Vector.h
class Vector
{
    public:
        Vector(const Vector& other);    // <- 받는 매개변수가 이러면 복사 생성자다

        // 무슨 소리냐면
        // const하고, 나 자신 클래스. 똑같은 클래스에 있는 개체를 하나 받는거.
        // 저건 생성자이므로 나 자신을 대입할 순 없음. 생성되는 도중이므로. 존재도 안하니까.
        // 같은 클래스에 있는 다른 개체를 매개변수로 받아서 생성하는 것.

    private:
        int mX;
        int mY;
};

// Vector.cpp
Vector::Vector(const Vector& other)
    : mX(other.mX), mY(other.mY)
{
}
  • 같은 클래스에 속한 다른 개체를 이용하여 새로운 개체를 초기화한다는 것
    • 같은 크기, 같은 데이터
    • <class-name> (const <class-name>&);
1
2
3
4
Vector(const Vector& other);

Vector a;       // 매개변수 없는 생성자를 호출
Vector b(a);    // 복사 생성자 호출
  • 코드에 복사 생성자가 없는 경우, 컴파일러가 암시적 복사 생성자를 자동 생성
  • 암시적 복사 생성자는 얕은 복사를 수행
    • 멤버별 복사
    • 각 멤버의 값을 복사함.
    • 개체인 멤버변수는 그 개체의 복사 생성자가 호출됨
  • 클래스 안에서 동적으로 메모리를 할당하고 있다면? 얕은 복사가 위험할 가능성이 높음
  • 직접 복사 생성자를 만들어서 깊은 복사를 할 것
    • 포인터 변수가 가리키는 실제 데이터까지도 복사
    1
    2
    3
    4
    5
    6
    7
    8
    
    // 깊은 복사
    
    ClassRecord::ClassRecord(const ClassRecord& other)
      : mCount(other.mCount)
    {
      mScores = new int[mCount];
      memcpy(mScores, other.mScores, mCount * sizeof(int));
    }
    

2. 연산자 오버로딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Vector.h
class Vector
{
    public:
        Vector operator+(const Vector& rhs) const;
        // 첫번째 매개변수는 나 자신, 그다음 연산자, 그리고 다른 벡터가 두번째
    private:
        int mX;
        int mY;
};

// Vector.cpp
Vector Vector::operator+(const Vector& rhs) const
{
    Vector sum;
    sum.mX = mX + rhs.mX;
    sum.mY = mY + rhs.mY;

    return sum;
}

// main.cpp
Vector v1(10, 10);
Vector v2(3, 17);
Vector sum = v1 + v2;               // <- 여기서 연산자 오버로딩
Vector sum = v1.operator+(v2);      // 위와 동일!

std::cout << number;
std::cout.operator<<(number);       // 위와 동일

2-1. friend 키워드

1
2
3
4
void operator<<(std::ostream& os, const Vector& rhs)
{
    os << rhs.mX << ", " << rhs.mY;
}

위 코드에는 문제점이 있음.

  • 이 전역함수를 어디에 넣지?
  • 이 함수가 Vector 클래스의 private 멤버변수를 어떻게 읽지?

💡 friend 함수를 쓰면 된다!

  • 클래스 정의 안에 friend 키워드를 사용 가능
    • 다른 클래스나 함수가 나의 private 또는 protected 멤버에 접근 할 수 있게 허용 ```cpp // X.h class X { private: int mPrivateInt; }

    // Y.h #include “x.h” class Y { public: void Foo(X& x); }

    // Y.cpp void Y::Foo(X& x) { x.mPrivateInt += 10; // 컴파일 에러 }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    고치려면...
    
    ```cpp
    // X.h
    class X
    {
      friend class Y;     // <------- 이렇게 친구라고 선언!
        
      private:
          int mPrivateInt;
    }
    

이래서 friend는 oop 안티 패턴이라고도 함… 외부에서 쓰게 해주니까.

friend 함수도 있음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// X.h
class X
{
    friend void Foo(X& x);  // <----- 이렇게 friend 써서 접근 가능하게 함
    
    private:
        int mPrivateInt;
}

// GlobalFunction.cpp
void Foo(X& x)
{
    x.mPrivateInt += 10;
}
  • friend 함수는 멤버 함수가 아님!
  • 하지만 다른 클래스의 private 멤버에 접근할 수 있음. 권한을 주는거임.

이게 정말 올바른가요?

1
2
3
void operator<<(std::ostream& os, const Vector& rhs);

std::cout << vector1 << std::endl;      // 컴파일 에러

왜 컴파일 에러가 나냐면,
cout « vector1 해서 반환되는 값을 받아서 또 endl이 호출이 되는 구조인데,
« 오퍼레이터는 반환형이 void임… 받을 게 없으니 에러가 남.

1
2
3
4
5
6
7
8
9
10
11
// 이렇게 고쳐주면 됨!
std::ostream& operator<<(std::ostream& os, const Vector& rhs);

std::ostream& operator<<(std::ostream& os, const Vector& rhs)
{
    os << rhs.mX << ", " << rhs.mY;

    return os;      // <- 요렇게 반환!
}

std::cout << vector1 << std::endl;      

2-2. 연산자 오버로딩과 const

  • const 를 쓰는 이유?
    • 멤버 변수의 값이 바뀌는 것을 방지
    • 최대한 많은 곳에 const를 붙일 것!
  • const &를 사용하는 이유?
    • 불필요한 개체의 사본이 생기는 것을 방지
    • 멤버 변수가 바뀌는 것도 방지
      1
      
      Vector operator+(const Vector& rhs) const;
      

이슈 및 공부한 것을 기록해두는 개인 블로그 입니다. 댓글, 피드백 환영합니다 🙂

Update:

댓글남기기