[C++] 06. friend, static, const

kkado·2023년 10월 14일
0

열혈 C++

목록 보기
6/16
post-thumbnail

💬 윤성우 님의 <열혈 C++ 프로그래밍> 책을 혼자 공부하며 배운 내용을 정리합니다. 글의 모든 내용은 책에서 발췌하였습니다.


const

const로 어떤 객체를 선언하면, 이 객체를 대상으로는 const 멤버함수만 호출이 가능하다. const 객체란 데이터의 변경을 허락하지 않는데, 변경할 수 있는 가능성이 있는 함수는 호출 자체를 제한한다.

따라서 멤버 변수를 호출하지 않는 함수는 가급적 const를 붙여서 const 객체에서도 호출할 수 있도록 할 필요가 있다. const 키워드는 많을수록 좋다.

const 오버로딩

앞서 오버로딩은 함수의 인자 수 또는 자료형이 달라야 한다고 했는데, const의 유무 또한 오버로딩의 조건에 해당된다.

따라서 다음과 같은 오버로딩은 가능하다.

void someFunc() {}
void someFunc () const {}

어떤 객체 안에 같은 함수가 상수형, 비상수형으로 정의되어 있다면, 함수를 호출하는 객체가 상수냐 비상수냐에 따라 호출되는 함수가 달라진다.


friend

friend는 친구라는 뜻이다. 친구에게는 내 사적인 비밀까지 말할 수 있다.

어떤 클래스 A가 다른 클래스 B를 친구로 선언하면, B에서는 A의 private 멤버에 접근할 수 있다.

class Boy
{
private:
    int height;
    friend class Girl;
public:
    Boy(int n) : height(n)
    {}
};

class Girl
{
public:
    void Func(Boy &frn)
    {
        cout << "Boy's height : " << frn.height << "\n";
    }
};


int main()
{
    Boy b(100);
    Girl g;
    g.Func(b);
}

Boy 클래스에서 Girl 클래스를 friend 선언했으므로, Girl 클래스에서는 Boy 클래스의 멤버 변수 heightprivate으로 선언되었음에도 불구하고 접근하여 출력할 수 있다.

함수의 friend 선언

전역 함수 혹은 클래스의 멤버 함수 또한 friend 키워드를 사용할 수 있다.
A 클래스에서 B 클래스의 멤버 함수 또는 전역 함수를 friend 선언하면, B 클래스 객체 혹은 전역에서 A 클래스의 private 멤버에 접근할 수 있다.

friend, 언제 사용하나?

객체지향의 의의는 정보의 은닉에 있다. 그러나 friend 문법은 이 정보은닉을 무너뜨리는 문법이다.

나같은 초보들의 경우 private 변수를 이곳저곳에서 사용하기 위해 friend를 남발하는 경우가 있다는데 이는 문제를 더 크게 만든다.

가급적 사용하지 않는 것이 좋다고 한다.


static

C++에서도 C의 static 키워드의 의미가 통용된다.

static의 의미를 잠깐 다시 상기하고 간다면 두 가지 개념으로 정리할 수 있다.

  • 컴파일 시에 데이터 영역에 딱 한번만, 딱 하나만 메모리 공간을 할당받는다.
  • 선언된 범위를 벗어나도 소멸하지 않는다.

각 객체가 몇 번 생성되었는지 카운트하기 위하여, 정적변수 말고 전역변수를 사용할 수 있다.
그러나 전역변수로 사용하게 되면 다른 모든 곳에서도 접근이 가능하기 때문에 좋지 않다. 따라서 클래스 내의 정적 변수로 선언하는 것이 좋다.

class C
{
public:
	static int count;

    C()
    {
        count += 1;
    }

    void printCount()
    {
        cout << "count : " << count << "\n";
    }
};

int C::count = 0;

int main()
{
    C c1;
    C c2;
    C c3;

    c1.printCount();
    c2.printCount();
    c3.printCount();
}

실행 결과 각각의 객체가 같은 위치의 정적 변수 count를 공유하기 때문에 생성 시마다 1씩 증가하여 3이 3번 출력된다.

이 정적 변수는 객체 내에 존재하지 않고, 다만 클래스 객체 내에서 접근할 수 있는 권한이 있다.

정적 변수는 컴파일 시에 이미 메모리 공간에 할당이 이뤄지기 때문에 객체의 생성자로 초기화하면 안 된다. 따라서 클래스 외부 전역에서 초기화를 진행해 주어야 한다.

그리고 정적 멤버변수가 위처럼 public 으로 선언이 되면 어디서든 접근이 가능하다. 객체 안에 존재하지 않기 때문이다.

int main()
{
    C c1;
    C c2;
    C c3;
    
    cout << C::count << "\n";
}

static 멤버 함수

정적 멤버 함수 역시 정적 멤버 변수와 동일한 특성을 가진다.

  • 객체의 멤버로 존재하지 않음
  • 선언된 클래스의 모든 객체가 공유할 수 있음
  • public으로 선언되면 클래스 이름을 이용해 호출이 가능함

다음과 같은 코드는 컴파일 에러를 발생시킨다.

class C
{
private:
    int num1;
    static int num2;

public:
    static void addNum(int n)
    {
        num1 += n; // 에러 발생!
        num2 += n;
    }
}

언뜻 보면 멤버함수가 멤버변수 안에 접근하니 문제가 없을 것 같지만, 정적 함수는 객체의 멤버로 존재하지 않는다 는 성질을 상기하면 이해가 가능하다.

  • 객체의 멤버가 아니니, 객체 내부의 private 변수에 접근이 불가능함
  • 컴파일 될 때 할당되니, 객체가 만들어지기 전부터 호출이 가능하다. 그럼 어떤 멤버 변수에 접근을 할 것인가.
  • 클래스의 어떤 객체의 멤버 변수의 값을 변경할 것인가.

정적 멤버함수 내에서는 정적으로 선언된 멤버함수/변수만 호출 가능하다.

따라서 위의 num2 += n; 은 문제없는 코드이다.

const static 멤버

챕터 4 에서, const 로 선언된 멤버 변수는 : num1(n) 와 같이 멤버 이니셜라이저를 이용해 초기화해야 한다고 하였다. 그러나 정적 상수 멤버 변수는 선언과 동시에 초기화를 할 수 있다.

그리고 정적 변수이기 때문에 클래스 객체가 생성되지 않았더라도 접근이 가능하다.

class C
{
public:
    const static int A = 100;
};

int main()
{
    cout << C::A << "\n";
    // 출력 결과 : 100
}

mutable

'변하기 쉬운' 이라는 뜻의 mutable 키워드는 const 함수 내에서의 값의 변경을 예외적으로 허용한다.

mutable은 가급적 사용을 지양해야 하는 키워드이긴 하다.
const 함수란, 그 함수 내에서 멤버 변수의 값을 변경하지 않겠다는 뜻이었다.

다음 코드를 살펴보자

class C
{
private:
    int num1;
    mutable int num2;
public:
    C(int n1, int n2) : num1(n1), num2(n2)
    {}

    void showData() const
    {
        cout << num1 << num2 << "\n";
    }

    void copyData() const
    {
        num2 = num1;
    }
};

int main()
{  
    C a(10, 20);
    a.showData();
    a.copyData();
    a.showData();
    /*
    실행 결과
    10 20
    10 10
    */
}

copyData 함수는 상수 멤버함수이다. 그러므로 멤버 변수의 값이 변경되면 안된다. 그러나 num2 = num1; 으로 num2의 값을 변경시키고 있다.

그러나 num2가 상수 함수 내에서도 값이 변할 수 있는 mutable 키워드로 선언되었기 때문에 문제를 발생시키지 않고, 실제로 값도 변한다.


profile
울면안돼 쫄면안돼 냉면됩니다

0개의 댓글