반응형
  • 스마트 포인터
    • 다른 타입의 포인터 역할을 하는 객체
  • 구현 원리
    • ->, * 연산자 재정의를 통해 포인터 처럼 보이게 함
    • * 연산자 재정의 할때는 반드시 참조 리턴
  • 장점
    • 객체이므로 생성자/복사생성자/대입연산자/소멸자 등을 통해서 생성/복사/대입/소멸의 과정을 제어 가능
    • 소멸자에서 동적 할당 자원 해지

스마트 포인터 직접 구현

class Car
{
public:
    void Go() { std::cout << "Go" << std::endl; }
    ~Car() { std::cout << "~Car" << std::endl; }
};

class SPtr // Car 포인터를 소유한 객체(스마트 포인터)
{
    Car* ptr;
public:
    SPtr(Car* p = 0) : ptr(p) {}
    Car* operator->() { return ptr; }
    Car& operator*() { return *ptr; }
};

int main()
{
    SPtr p = new Car; // SPtr p( new Car ) 원리
    p->Go(); // (p.operator->())->Go() 원리
    (*p).Go(); // (p.operator*())->Go() 원리
}

스마트 포인터 직접 구현<템플릿 버전>

  • 아래의 코드도 얕은 복사(Shallow Copy) 이슈는 있음
// 다양한 데이터 타입 지원을 위한 템플릿 활용
template<typename T>
class SPtr
{
    T* ptr;
public:
    SPtr(T* p = 0) : ptr(p) {}
    ~SPtr() { delete ptr; }
    T* operator->() { return ptr; }
    T& operator*() { return *ptr; }
    void print() { std::cout << *ptr << std::endl; }
};

int main()
{
    SPtr<int> p1 = new int; // SPtr<int> p( new int ) 원리
    *p1 = 10;
    p1.print();
}

C++ 표준 스마트 포인터 사용

  • Shared_ptr
    • C++ 표준 스마트 포인터
    • <memory> 헤더
    • 참조 계수 기반으로 구현됨
  • 주의사항
    • shared_ptr<Car> p1 = new Car; // error
    • shared_ptr<Car> p1 ( new Car ); // ok
    • explicit 생성자 때문에 = 사용 불가
#include <iostream>
#include <memory> // shared_ptr을 사용하기 위해

class Car
{
public:
    void Go() { std::cout << "Go" << std::endl; }
    ~Car() { std::cout << "~Car" << std::endl; }
};

int main()
{
    std::shared_ptr<Car> p(new Car); // = 생성자는 지원하지 않음

    p->Go();
}
반응형

반응형

연산자 재정의 활용

  • 전위형(++n), 후위형(n++) 연산자
    • 전위형, 후위형 모두 n.operator++ 연산자 재정의로 구현
    • 전위형은 자신을 참조로 리턴하여 구현
      • Integer& operator++()
    • 후위형은 구분을 위해 인자로 int를 받고 리턴값으로 증감을 반영하지 않기 위해 값으로 리턴
    • 후위형은 내부에서 전위형을 호출(재활용)하여 구현 
      • Integer operator++(int)
class Integer
{
    int value;
public:
    Integer(int n = 0) : value(n) {}

    void print() const
    {
        std::cout << value << std::endl;
    }

    // 전위형 - 값리턴.
    Integer& operator++()
    {
        ++value;
        return *this;
    }

    // 후위형 - 값리턴.
    Integer operator++(int)
    {
        Integer temp = *this;
        ++(*this); // 전위형 코드를 호출하여 재활용
        return temp;
    }
};

int main()
{
    Integer n1 = 2;
    Integer n2 = ++n1; // 전위 연산자 return : 3
    Integer n3 = 2; 
    Integer n4 = n3++; // 후위 연산자 return : 2

    n2.print();
    n4.print();
}
반응형

반응형

연산자 재정의 활용

함수 객체( Function Object )

  • 정의
    •  () 연산자를 재정의해서 함수처럼 사용 가능한 객체
    • "Function Object" 또는 "Functor" 라고도 부른다.
  • 장점
    • 특정 상황(다른 함수의 인자로 전달 될 때)에서는 일반 함수보다 빠르다(인라인 치환)
    • 상태를 가지는 함수( 멤버 데이터가 있고, 멤버 함수를 활용 가능)
struct Plus
{
    int operator()(int a, int b)
    {
        return a + b;
    }
};

int main()
{
    Plus p;
    int n = p(1, 2); // 함수 처럼 사용

    std::cout << n << std::endl; // p.operator()(1,2)
}

///////////////////////////////////////////////////////
// 모든 데이터 타입 지원을 위한 템플릿 적용 버전
template<typename T>
struct Plus
{
    T operator()(T a, T b)
    {
        return a + b;
    }
};

int main()
{
    Plus<int> p;
    int n = p(1, 2); // 함수 처럼 사용

    std::cout << n << std::endl; // p.operator()(1,2)
}

C++ 표준 함수 객체

  • <functional> 헤더
  • 이미 plus, minus, module, less, greater 등 다양하게 제공
  • C++ 레퍼런스 사이트 참고(http://www.cppreference.com)
#include <iostream>
#include <functional>

int main()
{
    std::plus<int> p;
    int n = p(1, 2); // 함수 처럼 사용
}

 

반응형

반응형

cout의 원리

  • cout은 ostream 타입의 객체
  • <<연산자를 각각의 primitive 타입에 대해서 연산자 오버로딩 한 것
    • cout << 5 : cout.operator<<( int )
    • cout << 3.4 : cout.operator<<( double )

ostream 구현

  • 모든 primitive 타입에 대해서 operator<<() 연산자 함수 제공
  • 연속 출력을 위해서 리턴 값을 자기 자신을 참조 리턴
  • 실제 화면 출력 원리
    • 각 OS가 제공하는 시스템 콜 사용
      • Windows : windows API - WriteFile()
      • Linux : Linux system call - write()
#include <cstdio>
using namespace std;

// std::cout 원리 파악을 위한 구현 예제
namespace std
{
    class ostream
    {
    public:
        ostream& operator<<(int n)
        {
            printf("%d", n);
            return *this;
        }
        ostream& operator<<(double d)
        {
            printf("%f", d);
            return *this;
        }
    };
    ostream cout;
}

int main()
{
    cout << 3; // ostream& operator<<(int n) 호출됨
    cout << 3.4; // ostream& operator<<(double d) 호출됨
    cout << 3 << 4; // 자기 자신을 참조로 리턴하므로 연속 출력 가능
}

유니 코드 출력 팁

  • cout의 정확한 타입은 ostream이 아닌 basic_ostream 템플릿
    • typedef basic_ostream<char> ostream;
    • typedef basic_ostream<wchar_t> wostream;
  • 유니코드 출력
    • ostream cout;
    • wostream wcout;

endl 원리

  • endl은 함수
    • cout << endl;
    • endl(cout);
  • hex, dec, alpha... 등은 모두 함수
    • 입출력 조정자 함수(i/o manipulator)
  • 사용자가 직접 만들 수 있음
    • tab 예제
#include <cstdio>
using namespace std;

namespace std
{
    class ostream
    {
    public:
        ostream& operator<<(int n)
        {
            printf("%d", n);
            return *this;
        }
        ostream& operator<<(double d)
        {
            printf("%f", d);
            return *this;
        }
        ostream& operator<<(char c) // os << '\n'; 지원 위한 연산자
        {
            printf("%c", c);
            return *this;
        }
        ostream& operator<<(ostream& (*f)(ostream&)) // endl 함수 포인터를 담기 위한 연산자
        {
            f(*this); // endl 함수에 자신의 객체 참조 전달
            return *this;
        }
    };
    ostream cout;

    ostream& endl(ostream& os) // 개행
    {
        os << '\n';
        return os;
    }
    
    ostream& tab(ostream& os) // 탭 예제
    {
        os << '\t';
        return os;
    }    
    
}

int main()
{
    cout << 3 << endl; // cout.operator<<( 함수 포인터 )
    cout << 3 << tab << endl; // cout.operator<<( 함수 포인터 )
}

cout 사용자 정의 객체 출력

  • operator<<() 연산자를 일반 함수로 제공하면 가능
class Complex
{
    int re, im;
public:
    Complex(int r = 0, int i = 0) : re(r), im(i) {}

    friend std::ostream& operator<<(std::ostream&, const Complex&);
};

// ostream의 연산자는 상수 함수가 아니므로 접근을 위해 const 객체를 사용하지 않음
std::ostream& operator<<(std::ostream& os, const Complex& c)
{
    os << c.re << "," << c.im; // 사용자 정의 객체의 멤버 데이터를 개별 출력
    return os;
}

int main()
{
    Complex c(1, 1);

    std::cout << c; // cout으로 사용자 정의 객체 출력
}

 

 

반응형

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ 증감 연산자(++, --)  (0) 2019.05.10
C++ 함수 객체(Function Object)  (0) 2019.05.10
C++ 연산자 재정의 - 기본 개념  (0) 2019.05.08
C++ static, const, this  (0) 2019.05.07
C++ 복사 생성자  (0) 2019.05.06

반응형

연산자 재정의(Operator Overloading) 개념

 

  • a + b 코드에서

    • a, b가 모두 primitive type(int, double등)일 경우 : 덧셈 수행

    • a, b중 하나라도 user defined type일 경우 : operator+() 함수를 찾게 됨

  • c1 + c2 코드를 컴파일러가 해석하는 방법

    • 우선 c1.operator+(c2) 멤버 함수를 찾게 됨

    • 없으면 operator+(c1, c2) 일반 함수를 찾게 됨

  • 멤버 함수 구현

    • +는 이항 연산자 이지만 객체 자신이 있으므로 operator+() 함수의 인자는 1개

  • 일반 함수 구현
    • operator+()의 함수 인자는 2개
    • 일반적으로 인자로 받은 객체 private 멤버 접근을 위해 friend 함수로 등록
class Complex
{
    int re, im;

public:
    Complex(int r = 0, int i = 0) : re(r), im(i) {}

    void print() const
    {
        std::cout << re << ", " << im << std::endl;
    }
   
    // + 연산자 재정의
    Complex operator+(const Complex& c)
    {
        Complex temp(re + c.re, im + c.im);
        return temp;
    }
    
    friend Complex operator+(const Complex& c1, const Complex& c2); // friend 접근자 등록
};

Complex operator+(const Complex& c1, const Complex& c2)
{
    // 일반함수에서 객체의 private 멤버에 접근이 필요하므로 friend 접근자 등록
    Complex temp(c1.re + c2.re, c1.im + c2.im);
    return temp;
}

int main()
{
    int n = 3 + 4; // OK

    Complex c1(1, 1);
    Complex c2(2, 2);
    Complex c3 = c1 + c2; // 1. c1.operator+(c2), 2. operator+(c1, c2)
    c3.print();
}

연산자 재정의 정리

  • 인자가 모두 primitive type인 경우는 재정의 불가
  • 재정의 불가 연산자
    • ".*", "::", "sizeof", "typeid", "static_cast", "dynamic_cast", "reinterpret_cast", "const_cast", "."
  • 멤버 함수와 일반 함수로 2가지 모두 제공 가능
  • 2가지 모두 제공시 멤버 함수 우선 호출
  • 1번째 인자가 user defined type이 아닌 경우는 일반 함수로만 만들 수 있음(객체가 아니므로 멤버 함수 제공 불가)
  • 멤버 함수로만 재정의 가능한 연산자
    • "=", "()", "[]", "->"
  • 새로운 연산자를 만들거나, 인자의 개수를 변경하거나, 연산자 우선순위를 변경 할 수 없음
  • 기본 파라미터 사용 불가

중요 연산자들

  • << : cout 의 원리

  • ++ : STL 반복자를 만드는 원리, 구현 과정 중요
  • -> : 스마트 포인터
  • () : 함수 객체
  • [] : 객체를 배열처럼 
  • = : 복사 생성자와 유사한 개념(Shallow copy / Deep copy)
반응형

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ 함수 객체(Function Object)  (0) 2019.05.10
C++ 연산자 재정의 - cout, endl 원리  (2) 2019.05.08
C++ static, const, this  (0) 2019.05.07
C++ 복사 생성자  (0) 2019.05.06
C++ 접근 지정자, 생성자, 소멸자  (0) 2019.05.06

반응형

static member data

  • static 멤버 변수 모양

    • 클래스 안에 선언(declaration) -> static 키워드 사용

    • 클래스 외부에 정의(definition) -> static 키워드 사용 안함

  • static 멤버 변수

    • 모든 객체가 공유(전역 변수와 유사)

    • 접근 지정자 사용 가능

    • 객체를 생성하지 않아도 메모리에 존재(전역)
    • 객체 생성시 static 멤버는 객체 메모리에 불포함(sizeof 객체로 해보면 static 멤버 제외 사이즈가 리턴)
    • "클래스 이름::변수 이름" 사용으로 클래스별 동일 이름 사용 가능
  • static 멤버 접근 방법
    • 클래스이름::멤버이름 : Car::cnt // 권장
    • 객체이름::멤버이름 : c1.cnt // 일반 멤버인지 static 멤버인지 구분 모호
class Car
{
    int speed;
public:
    static int cnt; // 선언(Declaration)
    Car() { ++cnt; }
    ~Car() { --cnt; }
};
int Car::cnt = 0; // 정의(Definition)

int main()
{
    // 1. 객체 없이도 메모리에 존재
    std::cout << Car::cnt << std::endl;

    // 2. 객체 메모리에 포함되지 않음
    Car c1, c2;
    
    std::cout << Car::cnt << std::endl; // 3. 접근 방법(클래스::멤버이름)
    std::cout << c1.cnt << std::endl; // 3. 접근 방법(객체.멤버이름)
}

static member function

  • static 멤버 함수

    • 객체 없이 호출 가능한 멤버 함수

    • 클래스이름::함수이름()으로 호출 가능

    • 객체이름.함수이름()으로도 호출 가능

    • 일반 멤버 함수 : 호출하려면 반드시 객체가 필요

class Car
{
    int speed;
public:
    static int cnt; // 선언(Declaration)
    Car() { ++cnt; }
    ~Car() { --cnt; }

    static int getCount() { return cnt; }
};
int Car::cnt = 0; // 정의(Definition)

int main()
{
    // 객체 생성과 무관하게 호출 가능(클래스::함수이름)
    std::cout << Car::getCount() << std::endl;

    Car c1, c2;
   
    // 객체를 통해서도 호출 가능(객체.함수이름)
    std::cout << c1.getCount() << std::endl;
}
  • static 멤버 함수 특징

    • 일반 멤버 data에 접근 불가

    • static 멤버 데이터만 접근 가능

    • 선언과 구현으로 분리할때 선언에만 static 표기

class Test
{
    int data1;
    static int data2; // 선언부에는 static 표기
public:
    static void f2()
    {
        data1 = 0; // 일반 멤버 data1에 접근 불가(컴파일 에러)
        data2 = 0; // static 멤버 data2에 접근 가능
    }
    static void f3(); // 선언부에는 static 표기
};

void Test::f3()  // 구현부 분리시 static 표기 안함
{

}

const member function

  • 상수 멤버 함수(const member function) 특징

    • 상수 멤버 함수 안에서는 모든 멤버를 상수 취급

    • 멤버의 값을 변경할 수 없음

    • 상수 멤버 함수 안에서는 상수 멤버 함수만 호출 가능

    • 상수 멤버 함수와 비상수 멤버 함수를 동일한 이름으로  제공 가능
class Point
{
public:
    int x, y;
    Point(int a, int b) : x(a), y(b) {}

    void set(int a, int b)
    {
        x = a;
        y = b;
    }
    
    void getX() const { return x; }
    void getY() const { return y; }

    void print() const // 상수 멤버 함수
    {
        getX() // const 멤버 함수 호출(OK)
        x = 10; // 멤버 데이터 변경(Error)
        set(1, 2); // 일반 멤버 함수 호출(Error)
        std::cout << x << ", " << y << std::endl; // 멤버 데이터 읽기(OK)
    }
};

int main()
{
    Point p(1, 2);
    p.x = 10;
    p.set(10, 10);
    p.print();
}
  • 상수 멤버 함수(const member function) 필요성

    • 상수 객체는 상수 멤버 함수만 호출할 수 있음

    • 객체의 상태를 변경하지 않는 모든 멤버 함수는 상수 함수가 되어야 함

    • getter 대부분 상수 멤버 함수가 되어야 함

class Point
{
public:
    int x, y;
    Point(int a, int b) : x(a), y(b) {}

    void set(int a, int b)
    {
        x = a;
        y = b;
    }

    void print() const; // 구현부를 분리시 선언, 구현부 모두 const 표시
};

void Point::print() const // 구현부를 분리시 선언, 구현부 모두 const 표시
{
    std::cout << x << ", " << y << std::endl;
}

// 자주쓰는 패턴
void foo(const Point& p)
{
    // p의 멤버함수가 상수 멤버함수로 제공되지 않는다면 호출 가능한 멤버가 없음
}

int main()
{
    const Point p(1, 2);
    p.print();  // 상수객체는 상수 멤버 함수만 호출 가능(const가 아닐경우 error)
}
  • mutable
    • 상수 멤버 함수 안에서 값을 변경 하고 싶을때
    • 논리적으로는 상수 멤버 함수지만 다양한 이유로 멤버의 값을 변경하고 싶을때
class Point
{
public:
    int x, y;
    int data = 0;
    mutable int fooCount = 0;

    const int* getPointer() const { return &data; }

    void foo() // 동일 이름의 상수 멤버 함수, 비상수 멤버 함수를 동시에 제공 가능
    {
        std::cout << "foo()" << std::endl;
    }
    void foo() const // 동일 이름의 상수 멤버 함수, 비상수 멤버 함수를 동시에 제공 가능
    {
        fooCount++; // 상수 멤버 함수에서 특별한 이유로 값 변경 필요시 mutable 지시자 선언
        std::cout << "foo() const" << std::endl;
    }
};


int main()
{
    Point p1;
    p1.foo(); // 1. 비상수 함수 호출, 2. 없을 경우 상수 함수 호출
}

this pointer

  • 내부 원리

    • 멤버 함수 호출 시 내부적으로 객체의 주소가 this라는 이름으로 전달됨

class Point
{
    int x = 0;
    int y = 0;
public:
    void set(int a, int b)
    {
        std::cout << this << std::endl; // main의 p1, p2 객체 주소랑 this는 동일 값
    }
};

int main()
{
    Point p1;
    Point p2;

    std::cout << &p1 << std::endl;
    p1.set(10, 20); // 내부적으로는 멤버 함수는 호출 시 객체의 주소가 추가로 전달됨

    std::cout << &p2 << std::endl;
    p2.set(10, 20); // 내부적으로는 멤버 함수는 호출 시 객체의 주소가 추가로 전달됨

}
  • 멤버의 이름과 지역변수(함수 인자)이름이 동일할때

    • 지역 변수가 우선시 됨

    • this를 사용하면 멤버 변수에 접근 가능

class Point
{
    int x = 0;
    int y = 0;
public:
    void set(int x, int y)
    {
        this->x = x; // 동일 변수명 사용시 지역변수 우선이지만 명시적인 구분을 위해 this 활용 가능
        this->y = y;
    }
};

int main()
{
    Point p1;
    p1.set(10, 20);
}
  • this를 리턴하는 함수

    • 멤버 함수 호출을 연속적으로 사용할수 있음

    • 자신(*this)을 리턴시 반드시 참조로 리턴 해야 함(return by reference 참조)

class Point
{
    int x = 0;
    int y = 0;
public:
    void set(int a, int b)
    {
        this->x = a;
        this->y = b;
    }

    Point* foo() { return this; } // this 이용 포인터 리턴
    Point& goo() { return *this; } // this 이용 값에 대한 레퍼런스 리턴
    //Point goo() { return *this; } // this 이용 값으로 바로 리턴시 임시 객체가 계속 복사됨 주의!
};

int main()
{
    Point p1;
    p1.foo()->foo()->foo(); // 포인터 재호출
    t.goo().goo().goo(); // 값 재호출
}
  • this 주의 사항
    • static 멤버 함수 안에서는 this를 사용 불가
    • static 멤버 함수 안에서는 this 사용이 불가하므로 멤버 데이터 접근이 불가능함
class Test
{
    int data;
public:
    static void foo()
    {
        std::cout << this << std::endl; // static 멤버 함수 내부에서는 this 사용 불가 Error
        data = 0; // this->data = 0; this 없어서 static 멤버 함수에서 일반 멤버 접근이 불가 Error
    }
};

int main()
{
    Test::foo(); // static 멤버 함수 호출(내부 this 객체 없음)
}
반응형

반응형

기본 개념

복사 생성자( Copy Constructor )

  • 객체가 자신과 동일한 타입으로 초기화 될때 사용

  • 사용자가 생성하지 않으면 컴파일러가 자동 생성(Default Copy Constructor)

  • 컴파일러가 제공하는 복사 생성자는 모든 멤버를 복사(Bitwise copy)

  • 필요한 경우 사용자가 직접 정의 가능

복사 생성자의 형태

  • Point( const Point& p)

class Point
{
public:
    int x;
    int y;
public:
    Point() : x(0), y(0) {} // 생성자 #1
    Point(int a, int b) : x(a), y(b) {} // 생성자 #2

    // 내부적으로 컴파일러에 의해 생성된 복사 생성자 형태
    // Point(const Point& p): x(p.x), y(p.y) {}
};

int main()
{
    Point p1; // 생성자 #1 호출
    Point p2(1, 2); // 생성자 #2 호출
    //Point p3(1); // 에러
    Point p4(p2); // 컴파일러가 생성한 복사 생성자 호출

    std::cout << p4.x << std::endl;
    std::cout << p4.y << std::endl;
}

복사 생성자가 호출되는 경우

  • 객체가 자신의 타입으로 초기화 될때

    • Point p2(p1);

    • Point p2{p1};

    • Point p2 = p1;

//객체가 자신의 타입으로 초기화 될때

class Point
{
    int x, y;
public:
    Point(int a = 0, int b = 0) : x(a), y(b) 
    {
        std::cout << "Point()" << std::endl;
    }

    Point(const Point& p): x(p.x), y(p.y)
    {
        std::cout << "Point(Point&)" << std::endl;
    }
};

int main()
{
    Point p1(1, 2); // 생성자
    Point p2(p1); // 복사 생성자
    Point p3{ p1 }; // 복사 생성자
    Point p4 = p1; // Point p4(p1) 복사 생성자
}
  • 함수 인자로 객체를 값으로 받을 때( call by value )

    • const &를 사용하면 복사 생성자/소멸자의 호출을 막을 수 있음

    • void foo( const Point& p);

// 함수 인자로 객체를 값으로 받을 때( call by value )

void foo(Point pt) // 이 순간 복사 생성자 호출
{

}

int main()
{
    Point p; // 생성자 호출
    foo(p); 
}
  • 함수가 객체를 값으로 리턴할 때( return by value )

    • RVO / NRVO

    • g++ 사용시 -fno-elide-constructors 옵션 이용하면 확인 가능

// 함수가 객체를 값으로 리턴할 때( return by value )

Point foo()
{
    Point pt(1, 2); // 생성자

    return pt; // 리턴용 임시객체 생성
}

int main()
{
    foo(); // 해당 라인동안만 임시객체는 유효
}

RVO(Return Value Optimization) / NRVO(Named RVO)

  • 임시 객체(Temporary Object)
    • "클래스이름()"으로 만드는 객체
    • 사용자가 직접 만들거나 컴파일러에 의해 임시 객체를 만드는 코드가 생성되기도 함
    • 단일 문장에서만 사용되며, 문장의 끝에서 파괴됨
    • 함수가 참조로 리턴하면 리턴용 임시객체가 생성되지 않음
    • 지역변수는 참조로 리턴할 수 없음
// RVO 적용 코드

Point foo()
{
    return Point(1, 1); // 객체 생성자체를 임시 객체로 생성하여 복사 생성을 방지
}

int main()
{
    foo();
}


// RVO 미적용 코드

// 내부 객체와 리턴용 임시 객체 총 2개가 생성된 코드
Point foo()
{
    Point pt(1, 1); // pt 생성 
    return pt; // pt를 복사한 리턴용 임시 객체생성
}

int main()
{
    foo();
}

Shallow Copy & Deep Copy

  • 얕은 복사( Shallow Copy )

    • 동적 할당된 메모리가 있을때 메모리 자체를 복사하지 않고 메모리 주소만 복사하는 현상

    • 소멸자에서 메모리를 삭제 하는 경우 문제 발생
    • 클래스 안에 포인터 멤버가 있고 동적 메모리 할당된 코드를 사용한다면 반드시 사용자가 복사 생성자를 만들어서 얕은 복사 문제를 해결 해야함

class Person
{
    char* name;
    int age;

public:
    Person(const char* n, int a) : age(a)
    {
        name = new char[strlen(n) + 1];
        strcpy_s(name, sizeof(name), n);
    }
    ~Person() { delete[] name; }
};

int main()
{
    Person p1("KIM", 2); // 단독 실행은 괜찮지만
    Person p2 = p1; // 이렇게 복사할 경우 runtime error(name을 같은 주소값을 가르키고, 소멸자에서 이중 해지 발생)
}
  • 깊은 복사( Deep Copy )

    • 주소가 아닌 메모리 내용 자체를 복사하는 것

class Person
{
    char* name;
    int age;

public:
    Person(const char* n, int a) : age(a)
    {
        name = new char[strlen(n) + 1];
        strcpy_s(name, sizeof(name), n);
    }
    ~Person() { delete[] name; }

    // 복사 생성자 직접 구현(Deep Copy)
    // 1. 포인터가 아닌 멤버는 그냥 복사(초기화 리스트)
    Person(const Person& p): age(p.age)
    {
        // 2. 메모리 할당
        name = new char[strlen(p.name) + 1];

        // 3. 메모리 내용을 복사
        strcpy_s(name, sizeof(name), p.name);
    }
};

int main()
{
    Person p1("KIM", 2);
    Person p2 = p1; // runtime error
}
  • 참조 계수(Reference Counting)

    • 얕은 복사를 기본으로 참조 카운트를 공유하여 최종 소멸시에만 메모리는 해지 하는 기법

// 참조 계수(Reference Counting)

class Person
{
    char* name;
    int age;
    int* ref; // 참조계수 메모리의 주소

public:
    Person(const char* n, int a) : age(a)
    {
        name = new char[strlen(n) + 1];
        strcpy_s(name, sizeof(name), n);

        ref = new int;
        *ref = 1;
    }

    // 소멸자에서 참조카운트 비교 후 최종 소멸시에만 동적 메모리 해지
    ~Person() 
    { 
        if (--(*ref) == 0)
        {
            delete[] name;
            delete ref;
        }
    }

    // 모든 멤버를 얕은 복사 후 참조 계수만 증가
    Person(const Person& p): age(p.age), name(p.name), ref(p.ref)
    {
        ++(*ref); // 참조 계수 증가
    }
};

int main()
{
    Person p1("KIM", 2);
    Person p2 = p1; // 복사 생성자.
}
  • 복사 금지(Delete Copy Constructor)

    • 복사 생성자를 삭제(delete)
      • 싱글턴 객체 생성시
      • C++ 표준의 unique_ptr
      • mutex 등의 동기화 객체
// 복사 금지

class Person
{
    char* name;
    int age;
    int* ref; // 참조계수 메모리의 주소

public:
    Person(const char* n, int a) : age(a)
    {
        name = new char[strlen(n) + 1];
        strcpy_s(name, sizeof(name), n);

        ref = new int;
        *ref = 1;
    }

    // 소멸자 얕은 복사
    ~Person() 
    { 
        if (--(*ref) == 0)
        {
            delete[] name;
            delete ref;
        }
    }

    // 복사 생성자 삭제
    Person(const Person& p) = delete;
};

int main()
{
    Person p1("KIM", 2);
    Person p2 = p1; // 복사 생성자.
}
  • 소유권 이전(Move)
    • 객체가 사용하던 자원을 전달 하는 개념
    • 버퍼 복사 등의 일부 알고리즘에서 사용시 성능향상을 볼수 있음
    • C++11의 move 생성자 개념
    • 복사 후 원본 객체는 사용하지 못한다는 가정(swap에서 유용)
class Person
{
    char* name;
    int age;

public:
    Person(const char* n, int a) : age(a)
    {
        name = new char[strlen(n) + 1];
        strcpy_s(name, sizeof(name), n);
    }

    ~Person() { }

    // 소유권 이전의 복사 생성자
    Person(Person& p): age(p.age), name(p.name)
    {
        // 원래 객체가 가진 name을 0으로 
        p.name = 0;
    }
};

int main()
{
    Person p1("KIM", 2);
    Person p2 = p1; // 복사 생성자.
}

 

  • C++ 표준라이브러리(STL) std::string을 활용
    • 자체적으로 내부 버퍼관리가 되므로 얕은 복사시 문제 없음
class Person
{
    std::string name;
    int age;

public:
    Person(std::string n, int a) : name(n), age(a)
    {
    }
};

int main()
{
    Person p1("KIM", 2);
    Person p2 = p1;
}
반응형

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ 연산자 재정의 - 기본 개념  (0) 2019.05.08
C++ static, const, this  (0) 2019.05.07
C++ 접근 지정자, 생성자, 소멸자  (0) 2019.05.06
C++ OOP(Object Oriented Programming)  (0) 2019.05.06
C++ Explicit Casting  (0) 2019.05.06

반응형

접근 지정자

  • 멤버 데이터를 외부에서 직접 변경하면 잘못된 값을 가질 수 있으므로 멤버 함수를 통해서만 변경하도록

  • 멤버 함수를 통해서 인자의 유효성 여부도 조사 가능

  • 접근 지정자

    • private : 멤버 함수에서만 접근 할 수 있음

    • public : 멤버 함수가 아닌 함수에서도 접근 가능

  • struct vs class

    • struct : 접근 지정자 생략시 기본 접근자가 public

    • class : 접근 지정자 생략시 기본값이 private

struct Bike // struct -> class로 변경시 기본 접근자가 private 이므로 아래 private 생략 가능
{
private:
    int gear; // private 접근 지정자로 외부에서 직접 접근 불가

public:
    void changeGear(int n)
    {
        if( n > 0 ) // 잘못된 파라미터 체크
            gear = n;
    }
};

int main()
{
    Bike b;
    b.changeGear(-10); // 안전하게 멤버 함수를 통한 접근만 허용
}
  • Friend
    • 멤버 함수는 아니지만 private 멤버에 접근 가능

    • 멤버 함수로 만들수 없는 특정 상황에서 사용

    • getter, setter 함수 제공과의 차이점

      • getter : 모든 함수가 멤버 데이터에 접근할 수 있음

      • friend : 약속된 함수만 멤버 데이터에 접근 할 수 있음

class Bike
{
private:
    int gear;

public:
    void changeGear(int n) { if( n > 0 ) gear = n; }

    // friend 함수 지정으로 멤버 함수가 아니지만 private 변수에 접근 허용
    friend void fixBike(); 

    // friend 클래스 지정으로 AAA클래스 모든 멤버 함수에서 private 변수 접근 허용
    friend class AAA;
};

class AAA
{
};

void fixBike()
{
    Bike b;
    b.changeGear = 0;
}

int main()
{
    Bike b;
    b.changeGear(-10);
}

생성자( Constructor )

  • 모양

    • 클래스 이름과 동일

    • 리턴 타입 표기 X

    • 파라미터 선택적

    • 2개 이상 생성 가능

  • 특징

    • 객체를 생성하면 자동으로 생성자가 호출됨

    • 생성자가 없으면 객체 생성 불가

    • 사용자가 생성자를 정의하지 않으면 컴파일러가 기본 생성자(Default Constructor)를 생성함

    • 생성자를 1개 이상 정의 해놓은 상태라면 기본 생성자가 생성되지 않음
class Point
{
    int x, y;
public:
    Point() { x = 0; y = 0; } // 생성자 #1
    Point(int a, int b) { x = a, y = b; } // 생성자 #2
};

int main()
{
    Point p1; // 생성자 #1 호출
    Point p2(1, 2); // 생성자 #2 호출
    Point p3{ 1, 2 }; // 생성자 #2 호출

    Point p4[3]; // 생성자 #1 3회 호출
    Point p5[3] = { Point(1, 2) }; // 생성자 #2 1회 호출, 생성자 #1 2회 호출

    Point* p6; // 생성자 호출 안됨(Point 포인터 메모리만 할당)
    p6 = static_cast<Point*>(malloc(sizeof(Point))); // 생성자 호출 안됨(Point 사이즈 메모리 할당)
    p6 = new Point(1, 2); // 생성자 #2 호출

    delete p6;
}
  • 호출 순서
    • 객체를 구성하는 멤버의 생성자가 먼저 호출 되고, 객체 자신의 생성자가 호출됨
// 생성자 호출 순서

class Point
{
    int x, y;
public:
    Point() { x = 0; y = 0; }
    Point(int a, int b) { x = a, y = b; }
};

class Rect
{
    Point p1;
    Point p2;
public:
    Rect() { std::cout << "Rect()" << std::endl; }
};

int main()
{
    Rect r; // 생성자 호출 순서 : p1 -> p2 -> Rect
}
  • default

    • 컴파일러에게 default 생성자 생성을 위임하는 방법(기본 생성자 내부에서 할일이 없는 경우 default 사용 권장)
class Point
{
    int x, y;
public:
    Point() = default; // 기본 생성자 컴파일러 생성 위임(C++11, x,y 값이 0으로 초기화)
    Point() {} // 기본 생성자 직접 생성(x, y 값 초기화 안됨)
};

int main()
{
    Point p1{};
}

소멸자( Destructor )

  • 모양

    • ~클래스 이름()
    • 리턴 타입 표기 안함
    • 파라미터 사용 불가
    • 1개만 생성 가능
  • 특징
    • 객체가 파괴될때 소멸자가 호출됨
    • 객체가 자원을 할당한 경우 소멸자에서 자원 해지 필요
    • 사용자가 소멸자를 만들지 않으면 컴파일러가 생성함
class Point
{
    int x, y;
    int* buf;
public:
    Point() 
    {
        std::cout << "Point()" << std::endl;
        buf = new int[10];
    }
    ~Point()
    {
        delete[] buf;
        std::cout << "~Point()" << std::endl;
    }
};

int main()
{
    Point p;
}

초기화 리스트

  • 생성자의 ()뒤에 : 을 표기하고 멤버를 초기화 하는것

  • 특징

    • 대입이 아닌 초기화

  • 주의사항

    • 멤버가 놓인 순서대로 초기화됨(다른 멤버의 초기화 값을 대입시에 주의)

// 초기화 리스트를 이용한 초기화
class Point
{
    int x, y;
    const int c;
public:
    Point() : x(0), y(0), c(10) // 초기화 리스트
    {
        
    }
};

int main()
{
    Point p;
}

// 대입방법으로 초기화
class Point
{
    int x, y;
    const int c = 10;
public:
    Point()
    {
        x = 0;
        y = 0;
    }
};

int main()
{
    Point p;
}
  • 초기화 리스트가 반드시 필요한 경우

    • 객체가 상수 멤버나 참조 멤버를 가진 경우

    • 기본 생성자가 없는 객체를 멤버로 가진 경우

class Point
{
    int x, y = 0;
public:
    Point(int a, int b) : x(a), y(b) {} // 생성자 #2
};

class Rect
{
    Point p1; // 기본 생성자가 없는 Point
    Point p2; // 기본 생성자가 없는 Point

    const int c;
    int& radius;
public:
    Rect(int& r) : p1(0,0), p2(0,0), c(0), radius(r) // 초기화 리스트에서 생성자 #2로 초기화 지정
    {
    }
};

int main()
{
    int radius = 15;
    Rect rect{ radius };
}

위임 생성자(Delegate Constructor)

  • 하나의 생성자에서 다른 생성자를 호출하는 문법
  • 초기화 리스트 구문에서 다른 생성자를 호출
  • C++11 이상
// Point() 생성자 호출 시 Point(int a, int b)를 호출하고 싶을때

class Point
{
    int x, y;
public:
    Point() : Point(0, 0) // 위임 생성자
    {
        Point(0, 0); // 생성자 호출이 아닌 임시 객체 생성 X
    }
    Point(int a, int b) : x(a), y(b) {} // 생성자 #2
};

 

멤버를 초기화 하는 3가지 방법

  • 생성자의 블록 안에서 초기화 -> 대입(상수, 인자 가능)

  • 초기화 리스트 -> 초기화(상수, 인자 가능)

  • 멤버 변수 선언시 초기화 -> 초기화(상수만 가능)

// 1. 대입 초기화
class Point
{
    int x;
    int y;
public:
    Point(int a, int b)
    {
        x = 0; // 파라미터, 상수 모두 사용 가능
        y = 0;
    }
};

// 2. 초기화 리스트 
class Point
{
    int x;
    int y;
public:
    Point(int a, int b) : x(a), y(b) // 상수, 파라미터 모두 사용 가능
    {
    }
};

// 3. 멤버 변수 선언 초기화
class Point
{
    int x = 0; // 생성자에서 받은 파라미터는 활용 불가, 상수만 가능
    int y = 0;
public:
    Point(int a, int b)
    {
    }
};
반응형

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ static, const, this  (0) 2019.05.07
C++ 복사 생성자  (0) 2019.05.06
C++ OOP(Object Oriented Programming)  (0) 2019.05.06
C++ Explicit Casting  (0) 2019.05.06
C++ reference 변수  (0) 2019.05.06

+ Recent posts