반응형

 

 

※ 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
반응형

 

다중 상속 (Multiple Inheritance)

- 부모 클래스를 둘 이상 동시에 상속하는 것을 말한다.

- C++은 다중 상속을 지원하는 언어이다.

- 득보다 실이 많은 문법이라 알아만 두고 사용은 하지말것.

  ※ JAVA, C#에서만 보더라도 다중 상속을 지원하지 않는다.

 

 

 

다중 상속의 모호성

1. 다중 상속의 둘 이상의 부모 클래스의 동일한 이름의 멤버가 존재하는경우 문제가 발생한다.

#include <iostream>
using namespace std;

class Parent_1
{
public:
    void Func()
    {
        cout << "Parent_1 func." << endl;
    }
};

class Parent_2
{
public:
    void Func()
    {
        cout << "Parent_2 func." << endl;
    }
};

class Child : public Parent_1, public Parent_2
{
public:
    void ChildFunc()
    {
        //에러
        Func();
        Func();
    }
};

void main()
{
    Child child;
    child.ChildFunc();
}
  • 코드를 실행하면 Func()함수의 모호성을 컴파일에러로 뱉는다.

- 위와 같은 모호성에는 다음과 같이 호출 함수 앞에 클래스를 명시해줘야한다.

void ChildFunc()
{
    Parent_1::Func();
    Parent_2::Func();
}

 

 

2. 다중상속의 대상이 되는 부모 클래스(B, C)가 같은 부모 클래스(A)를 상속을 한 클래스일 때, 자식 클래스의 객체 내에 두 개의 A 클래스 멤버가 존재하는 문제가 발생한다.

#include <iostream>
using namespace std;

class A
{
public:
    A()
    {
        cout << "A Constructor" << endl;
    }
    void Func()
    {
        cout << "A func." << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "B Constructor" << endl;
    }
};

class C : public A
{
public:
    C()
    {
        cout << "C Constructor" << endl;
    }
};

class D : public B, public C
{
public:
    D()
    {
        cout << "D Constructor" << endl;
    }
    void ChildFunc()
    {
        B::Func();
        C::Func();
    }
};

void main()
{
    D d;
    d.ChildFunc();
    
    //결과
    //A Constructor
    //B Constructor
    //A Constructor
    //C Constructor
    //D Constructor
    //A Func.
    //A Func.
}
  • ChildFunc() 함수를 보면 간접적으로 상속한 A 클래스의 Func()함수를 호출하기 위해 함수 앞에 A를 명시해 모호성을 제거해줬다.
  • 결과를 확인해보면 D 클래스의 객체에서 A 클래스가 2개 존재하는것을 확인할 수 있다.
  • 위와 같은 중복 상속을 해결하기 위한 문법이 '가상 상속'이다.

- 위 코드에서 A 클래스는 중복 상속될 필요가 없으므로 B, C 클래스에서 'virtual' 키워드로 가상 상속한다.

class B : virtual public A { . . };
class C : virtual public A { . . };
class D : public B, public c { . . };

//결과
//A Constructor
//B Constructor
//C Constructor
//D Constructor

 

반응형

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

C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
C++ 순수 가상함수, 추상 클래스  (0) 2021.11.11
C++ 업캐스팅, 오버라이딩, 가상함수  (0) 2021.11.08
반응형

 

멤버함수 동작원리

- C++의 객체의 멤버변수는 객체 내에 존재하지만 멤버함수는 다음과 같은 관계를 갖는다.

  • 멤버함수는 메모리의 한 공간에 별도로 위치한다.
  • 멤버함수가 정의된 클래스의 모든 객체가 이를 공유하는 형태를 취한다. (함수포인터를 생각하면 이해하기 편하다.)

 

 

 

 

가상함수 동작원리

- 예제 소스는 다음과 같다.

#include <iostream>
using namespace std;

class Parent
{
public:
    virtual void Func_1()
    {
        cout << "Parent : Func_1" << endl;
    }
    virtual void Func_2()
    {
        cout << "Parent : Func_2" << endl;
    }
};

class Son : public Parent
{
public:
    virtual void Func_1()
    {
        cout << "Son : Func_1" << endl;
    }
    void Func_3()
    {
        cout << "Son : Func_3" << endl;
    }
};

void main()
{
    Parent* parentPtr = new Parent();
    parentPtr->Func_1();
    
    Son* sonPtr = new Son();
    sonPtr->Func_1();
    
    //결과
    //Parent : Func_1
    //Son : Func_1
}

- 위의 코드에서 가상함수를 확인할 수 있는데, 한 개 이상의 가상함수를 포함하는 클래스에 대해서는 컴파일러가 '가상함수 테이블'이라는 것을 만든다. (V-Table이라고도 한다.)

- 가상함수 테이블은 Key와 Value를 가지고 있다.

  • Key : 호출하고자 하는 함수를 구분지어주는 구분자의 역할을 한다.
  • Value : 구분자에 해당하는 함수의 주소정보를 알려주는 역할을 한다.

- 따라서, Parent 클래스와 Son의 클래스의 가상함수 테이블은 다음과 같이 구성된다.

  • Parent 객체의 Func_1 함수를 호출하는 경우, Parent 테이블에서 첫 번쨰 행의 정보를 참조하여 Func_1 함수를 호출하게 된다.
  • Son 객체의 테이블을에서는 오버라이딩 된 가상함수의 Func_1에 대한 정보가 존재하지 않는다.
  • 즉, 오버라이딩 된 가상함수의 주소정보는 자식 클래스의 가상함수 테이블에 포함되지 않는다.
  • 결과적으로 오버라이딩된 가상함수를 호출하면, 가장 마지막에 오버라이딩을 한 자식 클래스의 멤버함수가 호출되게된다.

 

 

 

 

가상함수 테이블이 참조되는 방식

- 예제 소스가 실행되면, main함수가 호출되기 전에 가상함수 테이블이 메모리 공간에 할당된다.

  ※ 참고로 가상함수 테이블은 객체의 생성과 상관없이 메모리 공간에 할당된다. (가상함수 테이블이 멤버함수의 호출에 사용되는 일종의 데이터이기 때문.)

- main 함수가 호출되어 객체가 생성되고 나면, 각 Parent, Son 객체는 해당 클래스의 가상함수 테이블의 주소값이 저장된다.

  ※ 가상함수 테이블의 주소값은 우리가 직접 참조할수 있는 주소값이 아니고, 내부적 필요에 의해 참조되는 주소값이다.

- Parent 객체에서 Func_1함수를 호출하게되는 과정.

  • Parent 객체가 Parent 클래스의 가상함수 테이블 참조 → 100번지에 위치한 함수 실행

- Son 객체에서 Func_1함수를 호출하게 되는 과정.

  • Son객체가 Son 클래스의 가상함수 테이블 참조 → 200번지에 위치한 함수 실행

 

 

 

 

가상함수 테이블에 의한 속도의 저하

- 클래스에 가상함수가 포함되어 가상함수 테이블이 생성되면, 이 테이블을 참조하여 호출될 함수가 결정되기 때문에 실행속도가 감소하게된다.

- 하지만 극히 미미하고 속도차이를 감안하더라도 많은 장점을 제공하기 때문에 유용하게 활용되는것이다.

 

반응형

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

C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 순수 가상함수, 추상 클래스  (0) 2021.11.11
C++ 업캐스팅, 오버라이딩, 가상함수  (0) 2021.11.08
C++ 클래스 상속  (0) 2021.11.04
반응형

 

순수 가상함수(Pure Virtual Function)

- 함수의 몸체가 정의되지 않은 함수를 의미한다.

- 부모 클래스에서 사용하지 않는 함수이지만 자식 클래스는 무조건 동일한 이름으로 함수를 오버라이딩을 적용하게 강제하는 방법.

 

사용법

class A
{
public:
    virtual void Func1() = 0;
    virtual void Func2() = NULL;
    virtual void Func3() abstract;
};
  • 가상(virtual) 함수에 NULL(0) 값을 대입하거나 abstract를 붙여주면 해당 함수는 순수가상함수로 취급된다.
  • 순수 가상함수를 가진 클래스를 상속받은 클래스는 순수 가상함수의 몸체를 무조건 정의해주어야 한다.

 

 

추상 클래스(Abstract Class)

- 클래스 중에서는 객체생성을 목적으로 정의되지 않는 클래스도 존재한다.

- 멤버함수 중, 하나 이상 순수 가상함수로 선언하면 해당 클래스는 추상 클래스가 된다.

class B : public A
{
public:
    void Func1() override
    {
        cout << "Func1" << endl;
    }
    void Func2() override
    {
        cout << "Func2" << endl;
    }
    void Func3() override
    {
        cout << "Func3" << endl;
    }
};

void main()
{
    A a;	//컴파일 에러
    B b;
    b.Func1();
    b.Func2();
    b.Func3();
    
    //결과
    //Func1
    //Func2
    //Func3
}
  • A 클래스는 순수 가상함수를 포함하여 추상 클래스이기 때문에, 객체 생성시에 컴파일 에러가 발생한다.

 

 

반응형

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

C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
C++ 업캐스팅, 오버라이딩, 가상함수  (0) 2021.11.08
C++ 클래스 상속  (0) 2021.11.04
C++ 함수 오버로딩, Deault 매개변수  (0) 2021.10.24

+ Recent posts