반응형

 

 

 

가비지 컬렉터(GC, Garbage Collector)

- C/C++와 다르게 CLR(Common Language Runtime)이 자동 메모리 관리 기능을 제공한다.

- 객체를 힙에 할당을 하면 가비지 컬렉터는 이 중 쓰레기인것을 분리해서 수거해건다.

- 가비지 컬렉터 역시 소프트웨어이기 때문에 CPU와 메모리 자원을 소모한다.

   ※ 이 때, 별도의 공간에서 자원을 소모하는것이 아닌 사용자가 사용하는 자원을 같이 사용한다.

- unsafe 키워드를 사용한 비관리형 코드는 CLR이 제공하는 서비스를 받을 수 없다.

 

 

 

CLR의 메모리 관리

- C#으로 작성한 실행파일을 실행하면, CLR은 프로그램을 위한 일정 크기의 메모리(Managed Heap)를 마련한다.

- 그 뒤, CLR은 Managed Heap의 첫번째 주소에 객체를 할당할 메모리의 포인터를 위치시킨다.

- 힙에 객체를 할당하면 메모리 포인터를 할당된 객체가 차지하고 있는 공간 바로 뒤로 위치시킨다.

- Heap에 할당된 object 객체를 참조하고 있는 변수 obj가 Stack에서 소멸되버리면 object 객체는 어디에서도 접근할 수 없기 때문에 쓰레기가 되어버린다. (이런 객체들을 가비지 컬렉터가 수거해간다.)

- 위치 참조 객체(obj)를 Root라고 하고, 이 Root는 스택, 힙 아무데서나 생성될수 있다.

- .Net 어플리케이션이 실행되면 JIT 컴파일러(Just-In-Time Compile)가 이 Root들을 목록으로 만들고, CLR이 Root목록을 관리하면서 상태를 갱신한다.

   ※ 가비지 컬렉터가 CLR이 관리하는 루트의 목록을 참조해서 쓰레기를 수거해간다.

 

 

 

 

가비지 컬렉터 객체 정리 과정

1. 작업을 시작하기 전, 가비지 컬렉터는 모든 객체를 수거 대상으로 가정한다.

2. 루트 목록을 순회하면서 각 루트가 참조하고 있는 힙 객체와의 관계 여부를 조사한다. 만약 루트가 참조하고 있는 힙의 객체가 또다른 힙을 참조하고 있다면, 또 다른 힙 역시 루트와 관계있는것으로 판단하고 수거 대상에서 제외된다.

3. 쓰레기 객체가 차지하고 있던 메모리 공간은 비어있는 공간이 된다.

4. 루트 목록에 대한 조사가 끝나면, 가비지 컬렉터는 힙을 순회하면서 비어있는 공간에 쓰레기의 인접 객체들을 이동시켜서 채워넣는다.

 

 

 

 

세대별 가비지 컬렉션

- CLR의 메모리는 구역을 나누어 메모리에서 바로 없어질 객체와 오래 있을 객체를 따로 담아 관리한다.

- 메모리를 0, 1, 2의 3개 세대로 나누고 0세대에서는 빨리 사라질 객체, 마지막 세대에서는 오래 있을 객체로 채워진다.

- 객체에 대한 세대를 나누는 기준은 가비지 컬렉션을 겪은 횟수이다.

- 각 세대는 가비지 컬렉션 수행을 위한 임계치가 있으며, 이 임계치에 도달하면 가비지 컬렉터가 해당 세대에 대해 가비지 컬렉션을 수행하고, 살아남은 객체들을 다음 세대로 옮긴다.

- 상위 세대의 가비지 컬렉션이 수행되면 가비지 컬렉터는 해당 세대를 포함한 하위 세대에 대해서도 가비지 컬렉션을 수행한다.

  • 1세대 가비지 컬렉션 : 0, 1세대 가비지 컬렉션 수행.
  • 2세대(전체) 가비지 컬렉션 : 0, 1, 2세대 가비지 컬렉션 수행.

- 2세대 힙이 가득차게 되면, CLR은 어플리케이션의 실행을 잠시 멈추고 전체 가비지 컬렉션(Full GC)을 수행하여 메모리 확보하기 때문에, 어플리케이션의 메모리가 크면 클수록 Full GC시간이 길어지므로 유의해야한다.

 

 

 

 

가비지 컬렉션을 위한 효율적인 코드 작성법

1. 객체를 너무 많이 할당하지 말 것.

 - 객체 생성 코드를 작성할 때 곡 필요한 객체인지, 필요 이상으로 많은 객체를 생성하는 코드가 아닌지의 여부를 고려해야한다.

2. 너무 큰 객체 할당을 피한다.

 - CLR은 보통 크기의 객체를 할당하는 힙과는 별도로 85kb이상의 대형 객체를 할당하기 위한 대형 객체 힙(LOH : Large Object Heap)을 따로 유지한다. (사용자가 평소에 사용하는 힙은 소형 객체 힙(SOH : Small Object Heap)이다.)

 - SOH는 객체를 할당할 포인터가 위치한 메모리에 바로 객체를 할당하지만 LOH는 객체의 크기만큼의 여유 공간이 있는지 힙을 탐색하여 할당한다.

 - LOH는 메모리 정리를 위한 복사 비용이 비싸기 때문에, SOH처럼 정리된 힙에 객체들을 차곡차곡 모으지 않고 해제된 공간을 그대로 둔다.

 - 또한 LOH는 2세대 가비지 컬렉션이 수행되어야 쓰레기 객체가 수거되기 때문에 어플리케이션의 순간 정지를 불러온다.

3. 너무 복잡한 참조 관계는 만들지 말 것.

 - 객체끼리 너무 복잡한 참조관계를 만들어두면, 가비지 컬렉터는 가비지 컬렉션 수행 이후, 살아님은 객체의 세대를 옮기기 위해 메모리 복사를 수행하는 과정에서 객체를 구성하고 있는 각 필드 객체 간의 참조를 일일이 조사해서 참조 메모리 수조를 전부 수정하기 때문에 자원이 많이 소모된다.

 - 2세대의 객체의 멤버에 새로은 객체를 만들게될 경우 이 새로운 객체은 0세대 가비지 컬렉션에 의해 수거될 가능성이 있다. 이때 쓰기 장벽(Write barrier)이라는 장치를 통해 가비지 컬렉션의 대상에서 제외가 되는데, 여기서 발생하는 오버헤드가 크므로 참조관계를 최소한으로 하면 이러한 오버헤드를 줄일 수 있다.

4. 루트를 너무 많이 만들지 말 것.

 - 가비지 컬렉터는 루트 목록을 돌면서 쓰레기를 찾아내기 때문에, 루트 목록이 작아지면 그만큼 검사 수행 횟수가 줄어들므로 가비지 컬렉션이 빨리 끝나게된다.

 

 

 

 

 

반응형

'Stack > C#' 카테고리의 다른 글

C# 인덱서  (0) 2022.02.14
반응형

 

※ 연산자 오버로딩 기본 개념 참고

 

C++ 연산자 오버로딩

※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것. C++ friend friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다. ※ 그렇기 때문에, 클래스의 friend 선언은 객체

srdeveloper.tistory.com


 

 

대입 연산자

- 정의하지 않으면 디폴트 대입 연산자가 삽입된다.

- 복사 생성자와 마찬가지로 멤버 대 멤버의 복사가 일어난다.

- 디폴트 대입 연산자는 얕은 복사이기 때문에 깊은 복사가 필요하다면 직접 정의해야한다.

#include <iostream>
using namespace std;

class SomeClass
{
private:
    int n1;
    
public:
    SomeClass(int n1 = 0) : n1(n1) { }
    void ShowData()
    {
        cout << n1 << endl;
    }
    SomeClass& operator=(const SomeClass& ref)
    {
        cout << "SomeClass operator=()" << endl;
        this->n1 = ref.n1;
        return *this;
    }
};

void main()
{
    SomeClass sc1(10);
    SomeClass sc2;
    sc2 = sc1;
    sc2.ShowData();
    
    //결과
    //SomeClass operator=()
    //10
}
  • sc2 선언시 바로 대입 연산자를 사용하면 대입 연산자가 호출 되는것이 아닌 복사 생성자가 호출이 된다.

 

 

 

디폴트 대입 연산자의 문제점

- 복사 생성자에서 생기는 문제와 유사하다. (얕은 복사의 문제)

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;

class Name
{
private:
    char* name;
    
public:
    Name(const char* name) 
    {
        int len = strlen(name) + 1;
        this->name = new char[len];
        strcpy(this->name, name);
    }
    void ShowData()
    {
        cout << name << endl;
    }
    Name& operator=(const Name& ref)
    {
        delete[] name;
        int len = strlen(ref.name) + 1;
        this->name = new char[len];
        strcpy(this->name, ref.name);
        return *this;
    }
    ~Name()
    {
        delete[] name;
    }
};

void main()
{
    Name n1("Lee");
    Name n2("Yoon");
    n2 = n1;
    n2.ShowData();
    
    //결과 : Lee
}
  • 대입 연산자를 통한 얕은 복사에서 일어나는 메모리 누수를 막기위해 name 멤버 변수에 할당된 기존 데이터를 할당 해제하고 새롭게 할당하여 깊은 복사를 진행한다.

 

 

 

상속 구조에서의 대입 연산자

- 자식 클래스에서 대입 연산자를 오버로딩 하는 경우, 부모 클래스의 대입 연산자를 명시해 주지 않으면 부모 클래스의 대입 연산자는 호출이 되지 않는다.

- 따라서, 디폴트 대입 연산자의 경우 부모 클래스의 대입 연산자까지 호출되는 것을 확인 할 수 있다.

#include <iostream>
using namespace std;

class Parent
{
private:
    int n1, n2;
    
public:
    Parent(int n1 = 0, int n2 = 0) : n1(n1), n2(n2) { }
    void ShowData()
    {
        cout << n1 << " / " << n2 << endl;
    }
    Parent& operator=(const Parent& ref)
    {
        cout << "Parent operator=()" << endl;
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

class Child : public Parent
{
private:
    int n1, n2;
    
public:
    Child(int n1, int n2, int n3, int n4) : Parent(n1, n2), n1(n3), n2(n4) { }
    void ShowData()
    {
        Parent::ShowData();
        cout << n1 << " / " << n2 << endl;
    }
    Child& operator=(const Child& ref)
    {
        cout << "Child operator=()" << endl;
        //부모 클래스 대입 연산자 호출
        Parent::operator=(ref);
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

void main()
{
    Child c1(1, 2, 3, 4);
    Child c2(0, 0, 0, 0);
    c2 = c1;
    c2.ShowData();
    
    //결과
    //Child operator=()
    //Parent operator=()
    //1 / 2
    //3 / 4
}
  • Child 클래스에서 대입 연산자 오버로딩에 Parent::operator(ref)를 주석처리하고 실행해보면 부모 클래스의 멤버는 대입 연산자가 작용하지 않은것을 확인할 수 있다.

 

 

 

인덱스 연산자

- C, C++의 배열은 '경계검사를 하지 않는다'는 단점을 가지고 있다.

- 즉 배열의 범위에 벗어난 인덱스 값을 넣어도 실행하는데 무리없이 진행이 되기 때문에, 안정성을 고려해서 배열 클래스를 만든다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void main()
{
    IntArray ia(3);
    for(int i = 0; i <= 3; i++)
    {
        ia[i] = i + 1;
    }
    
    //결과 : Arr index out of range
}
  • 결과와 같이 배열에 대한 잘못된 접근을 차단함으로써 안정성을 보장받을 수 있다.

 

 

 

※ 배열 클래스의 복사에 대해서..

- 배열은 저장소의 일종이고, 저장소에 저장된 데이터는 '유일성'이 보장되어야 하기 때문에, 저장소의 복사는 잘못된 일로 간주된다.

- 따라서, 복사 생성자와 대입 연산자는 private 멤버로 지정하여 복사를 원칙적으로 막는것이 좋은 선택이 되기도 한다.

//IntArray 클래스의 private에 복사 생성자와 대입 연산자를 멤버로 둔다.
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }

 

 

 

 

const를 통한 배열 안정성 보장하기

- 어떠한 함수를 정의할 때, 매개변수를 const로 지정하면 해당 함수 내에서는 값의 변경이 불가능하므로 안정성을 보장받을 수 있지만, 위와 같은 코드에서는 인덱스 연산자에서 const 선언이 되어있지 않기 때문에 const로 지정된 매개변수로 받으면 컴파일 에러가 난다.

//전역함수
void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;	//컴파일 에러 발생
    }
}

 

- 해당 컴파일 에러를 해결하려면 const함수를 오버로딩하면 해결이 된다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;
    
    IntArray(const IntArray& ref) { }
    IntArray& operator=(const IntArray& ref) { }

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    //배열 안에 값을 넣을 때 호출됨.
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    //const 매개변수로 선언된 배열 안의 값을 찾을 때 호출된
    int operator[](int index) const
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;
    }
}

void main()
{
    IntArray ia(3);
    for(int i = 0; i < 3; i++)
    {
        ia[i] = i + 1;
    }
    ShowData(ia);
    
    //결과
    //1
    //2
    //3
}
  • 기존의 인덱스 연산자에서 const를 붙이는게 아닌 오버로딩을 한 이유는, 배열 멤버에 대한 저장이 불가능해지기 때문이다.
  • 따라서 const 선언을 이용해 인덱스 연산자를 오버로딩하여 해결한다.

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

namespace

- 특정 영역에 이름을 붙여주기 위한 문법적 요소

- namespace가 다르면 같은 이름의 선언이 허용된다.

- 여러 외부 라이브러리 사용 중 발생하는 문제를 해결하기 위해 만들어졌다.

- namespace는 다른 namespace 안에 삽입될 수 있다.

- namespace의 별칭 지정이 가능하다.

 

namespace 예제

#include <iostream>

namespace A
{
    void Func()
    {
        std::cout << "Namespace A : Func()" << std::endl;
    }
}

namespace B
{
    void Func()
    {
        std::cout << "Namespace B : Func()" << std::endl;
    }
}

void main()
{
    A::Func();
    B::Func();
    
    //결과
    //Namespace A : Func()
    //Namespace B : Func()
}
  • 위의 예제에서 ::을 범위지정 연산자라고 한다.

 

namespace 중첩 예제

#include <iostream>

namespace A
{
    int num = 0;
    
    namespace B
    {
        int num = 1;
    }
    
    namespace C
    {
        int num = 2;
    }
}

void main()
{
    std::cout << A::num << std::endl;
    std::cout << A::B::num << std::endl;
    std::cout << A::C::num << std::endl;
    
    //결과
    //0
    //1
    //2
}

 

 

namespace 별칭 지정 예제

#include <iostream>

namespace A
{
    namespace B
    {
        int num = 1;
    }
}

void main()
{
    namespace AB = A::B;
    std::cout << AB::num << std::endl;
    
    //결과
    //1
}

 

 

 

 

using

- namespace에 선언된 모든것에 대해 namespace 지정의 생략을 명령한다.

- 편리함은 증가하지만 무분별하게 사용하면 충돌이 발생할 확률이 상대적으로 높아진다.

 

using 예제

#include <iostream>
using namespace A;
using namespace C;

namespace A
{
    int num = 0;
    
    namespace B
    {
        int num = 1;
    }
	
    namespace C
    {
        int num = 2;
    }
}

void main()
{
    std::cout << num << std::endl;		//모호함으로 인해 에러 발생
    std::cout << B::num << std::endl;	//namespace A 생략 가능
    std::cout << C::num << std::endl;	//namespace A 생략 가능
}

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 연산자 오버로딩 (대입, 인덱스 연산자)  (0) 2021.12.26
C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것.

 

C++ friend

friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다. ※ 그렇기 때문에, 클래스의 friend 선언은 객체 지향의 정보 은닉 개념에 위배된다. - friend는 사용되는 클래스에서 선

srdeveloper.tistory.com

 

연산자 오버로딩

- 사용자 정의 자료형(클래스)에 연산자를 재정의한다.

- operator 키워드와 연산자를 묶어서 함수이름을 정의하면 연산자 오버로딩이 이루어진다.

  • 예 : class operator+(const class& ref)

- 연산자 오버로딩은 멤버함수에 의한 연산자 오버로딩, 전역함수에 의한 연산자 오버로딩이 있다.

  • 멤버함수 연산자 오버로딩 : class.operator+(class)
  • 전역함수 연산자 오버로딩 : class.operator+(class, class)

 

 

  • 멤버함수, 전역함수 연산자 오버로딩 예제
#include <iostream>
using namespace std;

class OverloadingOP
{
private:
    int m_iNum1, m_iNum2;

public:
    OverloadingOP(int n1 = 0, int n2 = 0) : m_iNum1(n1), m_iNum2(n2) { }
    void ShowMember()
    {
        cout << m_iNum1 << " / " << m_iNum2 << endl;
    }
    //멤버함수 연산자 오버로딩
    OverloadingOP operator+(const OverloadingOP& ref)
    {
        return OverloadingOP(m_iNum1 + ref.m_iNum1, m_iNum2 + ref.m_iNum2);
    }
    //전역함수 연산자 오버로딩
    friend OverloadingOP operator+(const OverloadingOP& ref1, const OverloadingOP& ref2);
};

OverloadingOP operator+(const OverloadingOP& ref1, const OverloadingOP& ref2)
{
    return OverloadingOP(ref1.m_iNum1 + ref2.m_iNum1, ref1.m_iNum2 + ref2.m_iNum2);
}

void main()
{
    OverloadingOP op1(3, 4);
    OverloadingOP op2(10, 10);
    OverloadingOP op3 = op1 + op2;
    
    op3.ShowMember();
    
    //결과 : 13 / 14
}

 - 전역함수로 연산자 오버로딩을 할 시, 해당 클래스의 멤버에 접근할수 있어야하므로 friend 선언을 해준다. (friend 키워드의 적절한 사용예)

 

 

 

연산자 오버로딩 주의점

- 연산자 오버로딩이 멤버함수, 전역함수로 둘 다 존재할 경우, 멤버함수로 오버로딩된 함수가 우선시되어 호출되므로, 특별한 경우가 아니라면 멤버함수 기반으로 연산자 오버로딩을 하는것이 좋다.

- 매개변수의 디폴트값 설정이 불가능하다.

- 연산자의 우선순위와 결합성은 바뀌지 않는다. (연산자가 지니는 우선순위와 결합성은 그대로 따라간다.)

 

 

 

단항연산자의 오버로딩

- 단항연산자(++, --)는 매개변수가 없다.

  • 멤버함수 연산자 오버로딩 : class.operator++()
  • 전역함수 연산자 오버로딩 : operator++(class)

- 단항연산자의 전위, 후위 구분은 int로 구분한다.

  • 전위 : operator++()
  • 후위 : operator++(int)

  ※ int는 데이터를 전달하는 뜻이 아닌, 단순히 전위와 후위를 구분하기 위해서 사용하는것이다.

 

 

  • 단항연산자 오버로딩 예제
#include <iostream>
using namespace std;

class OverloadingOP
{
private:
    int m_iNum1, m_iNum2;

public:
    OverloadingOP(int n1 = 0, int n2 = 0) : m_iNum1(n1), m_iNum2(n2) { }
    void ShowMember()
    {
        cout << m_iNum1 << " / " << m_iNum2 << endl;
    }
    //전위
    OverloadingOP& operator++()
    {
        m_iNum1++;
        m_iNum2++;
        return *this;
    }
    //후위
    const OverloadingOP operator++(int)
    {
        const OverloadingOP returnValue(m_iNum1, m_iNum2);
        m_iNum1++;
        m_iNum2++;
        return returnValue;
    }
};

void main()
{
    OverloadingOP op(5, 10);
    (++op).ShowMember();
    (op++).ShowMember();
    
    //결과
    //6 / 11
    //6 / 11
}

 - C++가 ++(++obj)의 연산은 허용하되 (obj++)++를 허용하지 않는 연산특성을 가지고 있으므로 후위증감연산자의 경우 반환값을 const로 지정한다.

 

 

 

 

단항연산자의 오버로딩 시 교환법칙의 문제 해결하기

- 교환법칙 : A + B와 B + A의 결과가 서로 같음을 뜻한다. 즉, 연산자를 중심으로 한 피연산자의 위치는 연산 결과값이 같다는 법칙이다. (곱셈, 덧셈 연산이 있음)

- 자료형이 다른 두 피연산자를 대상으로 연산을 해야하는 경우, 형 변환으로 하여 자료형을 맞춘 다음에 연산이 이루어져야한다.

- 연산자 오버로딩을 이용하면 위와 같은 규칙에 예외를 둘 수 있다.

 

 

  • 교환법칙 예외두기 예제
#include <iostream>
using namespace std;

class OverloadingOP
{
private:
    int n;

public:
    OverloadingOP(int x) : n(x){ }
    void ShowMemver()
    {
        cout << n << endl;
    }
    //이 오버로딩은 OverloadingOP * int만 허용한다.
    OverloadingOP operator*(int i)
    {
        return OverloadingOP(n * i);
    }
    //이 오버로딩은 int * OverloadingOP만 허용한다.
    friend OverloadingOP operator*(int i, OverloadingOP& ref);
};

OverloadingOP operator*(int i, OverloadingOP& ref)
{
    return OverloadingOP(ref.n * i);
    //or
    return ref * i;
}

void main()
{
    OverloadingOP op1(5);
    OverloadingOP op2 = op1 * 2;
    OverloadingOP op3 = 2 * op1;
    
    op2.ShowMemver();
    op3.ShowMemver();
    
    //결과
    //10
    //10
}

 - 멤버함수의 형태로 오버로딩을 하게되면, 멤버함수가 정의된 클래스의 객체가 오버로딩 된 연산자의 왼편에 있어야 컴파일이 가능해진다.

 - 우측에 클래스의 객체를 둔 연산이 필요할 때에는 전역함수의 형태로 오버로딩을 해야 컴파일이 가능해진다.

 

 


 

C++ 연산자 오버로딩 (대입, 인덱스 연산자)

※ 연산자 오버로딩 기본 개념 참고 C++ 연산자 오버로딩 ※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것. C++ friend friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선

srdeveloper.tistory.com

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 연산자 오버로딩 (대입, 인덱스 연산자)  (0) 2021.12.26
C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

friend 선언

- 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다.

  ※ 그렇기 때문에, 클래스의 friend 선언은 객체 지향의 정보 은닉 개념에 위배된다.

- friend는 사용되는 클래스에서 선언을 해줘야한다.

- friend 선언은 클래스나 특정 멤버함수, 전역함수를 대상으로 사용이 가능하다.

 

  • 클래스 대상 friend
#include <iostream>
using namespace std;

class A
{
private:
    int n1;
    float f1;

public:
    A(int i, float f) : n1(i), f1(f) { }
    friend class B;
};

class B
{
private:
    int n1;
    float f1;

public:
    B() { }
    void Show()
    {
        cout << n1 << " / " << f1 << endl;
    }
    void Copy(A& a)
    {
        this->n1 = a.n1;
        this->f1 = a.f1;
    }
};

void main()
{
    A a(10, 10.5f);
    B b;
    b.Copy(a);
    b.Show();
    
    //결과 : 10 / 10.5
}

 

 

  • 멤버함수, 전역함수 대상 friend
#include <iostream>
using namespace std;
class Point;

class PointCalc
{
public:
    PointCalc() { }
    Point Add(const Point& p1, const Point& p2);
};

class Point
{
private:
    int x, y;

public:
    Point(int _x, int _y) : x(_x), y(_y) { }
    //1. PointCalc의 Add 함수에 대해 friend선언을 하고있다.
    friend Point PointCalc::Add(const Point& p1, const Point& p2);
    //2. 전역함수에 대해 friend선언을 하고있다.
    friend void ShowPoint(const Point& p1);
};

//1. 멤버함수 구현
Point PointCalc::Add(const Point& p1, const Point& p2)
{
    return Point(p1.x + p2.x, p1.y + p2.y);
}

//2. 전역함수 구현
void ShowPoint(const Point& p1)
{
    cout << "x : " << p1.x << " / y : " << p1.y << endl;
}

void main()
{
    Point p1(10, 20);
    Point p2(30, 40);
    PointCalc pc;
    
    ShowPoint(pc.Add(p1, p2));
    
    //결과 : x : 40 / y : 60
}

 

 

 

 

friend 선언시 주의점

- 위에서 언급했듯이 friend선언을 하게되면 모든 멤버에 접근이 가능하게 되므로 객체지향의 은닉성을 위배한다.

- 얽혀있는 클래스의 관계를 풀기위한 friend선언은 더 큰 문제를 야기하므로 사용하지 않는것이 좋다.

- friend 선언의 좋은 예는 연산자 오버로딩을 할 시, 교환법칙을 성립시키기 위한 사용법이 있다.

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ 연산자 오버로딩  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
C++ 순수 가상함수, 추상 클래스  (0) 2021.11.11

+ Recent posts