12. 클래스의 정의
클래스는 개념을 정의하고 객체는 실제 데이터 비트를 담는다.
따라서 각 개체는 서로 다른 mValue 변숫값을 가진다.
그러나 메서드의 구현 내용은 모든 객체가 공유한다.
class SpreadsheetCell
SpreadsheetCell 인스턴스1
SpreadsheetCell 인스턴스2
SpreadsheetCell 인스턴스3
mValue
mValue
mValue
void setValue(double inValue);
double getValue() const;
void setValue(double inValue);
double getValue() const;
void setValue(double inValue);
double getValue() const;
≠≠
==
15. 클래스의 정의
접근자 의미 활용 예
public
객체의 public 메서드 또는 public 멤버 변수는
다른 코드에서 제한 없이 호출하거나 읽고 쓸 수 있다.
사용자 측에서 이용하도록 공개하고 싶은
메서드나 private 또는 protected
멤버 변수에 대한 외부 접근 메서드에
적용한다.
protected
같은 클래스 안에서만 protected 메서드나
멤버 변수를 호출하거나 읽고 쓸 수 있다.
그 클래스의 서브클래스에서도 protected 메서드나
멤버 변수에 자유롭게 접근 할 수 있다.
사용자에게는 노출하지 않고
내부적으로 이용하기 위한 편의 메서드와
데이터 멤버들에 적용한다.
private
private 메서드나 멤버 변수는 해당 클래스 안에서만
접근 할 수 있고 그 클래스의 서브클래스에서도
접근이 불허된다.
서브클래스에서의 접근을 막고 싶은
항목들에 적용한다.
16. 메서드의 정의
::을 스코프 지정 연산자(Scope Resolution Operator)라고 한다.
setValue() 메서드가 SpreadsheetCell 클래스에 속한 것임을 알려준다.
17. 메서드의 정의
setValue() 메서드 안에서 mValue 멤버에 접근해 값을 바꿀 수 있다.
서로 다른 두 객체에서 호출된다면 같은 라인의 코드이지만 서로 다른 mValue를 바꾸게 된다.
SpreadsheetCell 인스턴스1
mValue
SpreadsheetCell 인스턴스2
mValue
≠
22. 메서드의 정의
메서드에는 호출된 객체에 접근할 수 있는 숨겨진 파라미터를 넘겨받는다.
이 파라미터를 this 포인터라고 하며 이 포인터를 통해 데이터 멤버에도 접근하거나
메서드를 호출할 수 있는 것은 물론 다른 메서드의 파라미터로서 전달할 수도 있다.
23. 메서드의 정의
SpreadsheetCell 클래스의 멤버 변수 이름을 mValue에서 value로 바꾸고,
setValue() 메서드의 파라미터 이름도 inValue에서 value로 바꿨다고 하자.
어느 value가 클래스 멤버이며, 어느 value가 메서드의 파라미터인가?
33. 객체의 생성
객체는 스택 변수로 선언하거나 new 또는 new[] 연산자를 호출할 때 생성된다.
객체가 될 때는 내부에 포함된 객체들도 함께 생성된다.
34. 객체의 생성
변수를 선언할 때 초깃값을 함께 주면 편리하게 사용할 수 있다.
마찬가지로, 객체를 생성할 때도 초깃값을 줄 수 있다. → 생성자(Constructor)
변수
생성자
클래스
35. 객체의 생성
생성자는 문법적으로 볼 때 클래스와 이름이 같은 메서드이다.
단, 생성자는 리턴값을 가지지 않으며, 파라미터가 있을 수도 있고 없을 수도 있다.
파라미터가 없는 생성자를 디폴트 생성자(Default Constructor)라고 부른다.
36. 객체의 생성
스택 객체에 대해서 생성자를 올바로 호출하는 방법은
변수 선언에 직접 생성자 파라미터를 넣는 방법 뿐이다.
생성자를 메서드로 직접 호출할 수 없다.
생성자를 나중에 호출할 수도 없다.
37. 객체의 생성
힙에 동적으로 객체를 생성할 때는 스택 객체와는 달리
SpreadsheetCell 객체의 포인터 변수를 선언할 때 바로 호출하지 않는다.
대신 new 연산자를 이용해서 실제로 객체를 생성하는 시점에
생성자 파라미터를 클래스명에 전달하면서 생성자를 호출한다.
포인터 변수를 선언할 때는 nullptr로 초기화하는 것이 바람직하다.
new를 이용해 생성한 객체는 delete를 이용해서 해제해 주어야 한다.
38. 객체의 생성
C++에서는 파라미터의 구성(개수, 타입)이 다르면 같은 이름의 함수(생성자)를 허용한다.
함수 호출 시 어떤 함수가 이용될지는 컴파일러가 호출에 사용된 파라미터 구성을
각 함수와 비교해서 결정하게 된다.
이러한 메커니즘을 오버로딩(Overloading)이라고 한다.
40. 객체의 생성
객체의 배열은 두 가지 단계로 생성될 수 있다.
모든 객체에 대해 연속된 메모리를 확보한 다음, 각 객체의 디폴트 생성자를 호출한다.
C++에서는 디폴트 생성자가 없는 클래스에 대해서는 객체 배열을 생성할 수가 없다.
2연속된 메모리 확보
SpreadsheetCell() SpreadsheetCell() SpreadsheetCell()
각 객체의 디폴트 생성자 호출1
42. 객체의 생성
컴파일러는 함수명이 myCell이고 리턴 타입이 SpreadsheetCell인 함수 선언으로 착각한다.
다음 라인에서는 함수 이름에 대해 객체처럼 도트 연산자를 사용했기 때문에 오류가 발생한다.
그런데 new 연산자로 힙에 객체를 생성할 때는 디폴트 생성자를 함수 호출 형태로 사용해야 한다.
43. 객체의 생성
정의된 생성자가 하나도 없을 때 컴파일러가 자동으로 디폴트 생성자를 만들어 준다.
컴파일러가 생성한 디폴트 생성자는 클래스 안에 속한 멤버들에 대해서도 디폴트 생성자를 호출한다.
단 int, double과 같은 기본 타입들은 생성자 호출이 없다.
45. 객체의 생성
C++11 표준에서는 명시적인 디폴트 생성자(Explicitly Defaulted Constructor)를 지원한다.
이것을 사용하면 구현부 없이도 디폴트 생성자를 간편하게 선언할 수 있다.
C++11에서는 명시적으로 삭제된 생성자(Explicitly Deleted Constructor)라는 개념도 지원한다.
이 기능을 사용하면 컴파일러가 자동으로 생성하는 디폴트 생성자까지 포함하여
생성자가 전혀 정의되지 않은 클래스를 만들 수 있다.
명시적인 디폴트 생성자 명시적으로 삭제된 생성자
46. 객체의 생성
C++은 생성자 초기화 리스트(Constructor Initializer)라 부르는
데이터 멤버 초기화 방법을 제공한다.
리스트는 콜론으로 시작해 각 항목이 쉼표로 나뉜다.
각 항목은 데이터 멤버를 함수 호출 형태로 괄호 사이에 초깃값을 받는다.
47. 객체의 생성
C++에서 객체를 생성할 때는 생성자를 호출하기 전에
모든 데이터 멤버들이 먼저 생성되어 메모리에 할당된 상태여야 한다.
이 때 객체 타입인 데이터 멤버는 생성자가 호출되고
기본 타입들은 그 데이터가 할당된 메모리에 남아있는 임의의 값을 가지게 된다.
mValue = ?
mString = “”
mValue = 0
mString = “”
48. 객체의 생성
생성자 초기화 리스트는 이러한 과정에서의 멤버에 대한
생성자 호출과 기본 타입 데이터의 초깃값을 선택할 수 있게 해준다.
어차피 실행된 과정을 이용하는 것이기 때문에
나중에 생성자 바디에서 코드를 통해 초기화하는 것보다 훨씬 효율적이다.
49. 객체의 생성
어떤 데이터 타입들은 생성자 초기화 리스트가 아니면 초기화할 수가 없다.
데이터 타입 설명
const 데이터 멤버
const 변수는 그 변수가 생성된 이후에 값을 대입할 수 없다.
이 변수에 대한 초깃값 할당은 반드시 변수의 생성 시점에 되어야 한다.
참조 타입 데이터 멤버 참조 타입은 무엇인가를 참조하지 않은 채로는 존재할 수 없다.
디폴트 생성자가 없는 객체 타입 멤버
C++에서는 객체 멤버들을 디폴트 생성자를 이용해 생성한다.
만약 디폴트 생성자가 없는 멤버가 있으면 그 객체를 초기화할 수가 없다.
디폴트 생성자가 없는 슈퍼클래스 이후에 설명할 예정
50. 객체의 생성
생성자 초기화 리스트에서는 초기화 나열 순서에 따라 멤버가 초기화되는 것이 아니라
클래스 정의 시점에 멤버들이 등장하는 순서에 따라 초기화된다.
(X)
(O)
51. 객체의 생성
C++에서는 복제 생성자(Copy Constructor)라는 특별한 생성자가 있다.
이 생성자는 객체의 복제본을 만들어야 할 때 편리하게 사용된다.
프로그래머가 명시적으로 복제 생성자를 만들지 않으면 컴파일러가 자동으로 만들어준다.
52. 객체의 생성
함수를 호출하면서 파라미터를 넘길 때는 기본적으로 그 값이 복제되어 전달된다.
이러한 방식을 값에 의한 전달(Pass-by-Value)이라고 한다.
클래스 객체를 함수나 메서드의 인자로 전달하게 되면 복제 생성자가 호출되어 만들어진다.
53. 객체의 생성
함수/메서드를 호출할 때 파라미터로 넘겨지는 객체의 복제 오버헤드를 피하려면 참조형 타입을 이용하면 된다.
객체가 참조로 전달되면 그 주소 값만 복제되고 내용은 복제되지 않기 때문에 값에 의한 전달보다 더 효율적이다.
또한 복제 오버헤드를 피하기 위한 의도만으로 참조형 타입을 이용한다면 const 키워드를 붙이는 것이 안전하다.
54. 객체의 생성
디폴트 생성자와 마찬가지로, 복제 생성자도 default나 delete 키워드를 이용해
컴파일러의 자동생성을 명시적으로 지정하거나 방지할 수 있다.
55. 객체의 생성
C++11에 추가된 initializer_list 생성자는 첫 번째 파라미터로 std::initializer_list<T> 타입을
넘겨받으면서 다른 파라미터가 없거나, 디폴트 값이 지정된 파라미터만 가지는 생성자를 말한다.
std::initializer_list<T>를 사용하기 위해서는 <initializer_list>를 인클루드 해야 한다.
56. 객체의 생성
C++11 이전 버전에서는 생성자의 바디나 생성자 초기화 리스트에서만 멤버 초기화를 할 수 있었다.
그러나 C++11에서는 멤버 변수의 초기화를 클래스를 정의하는 시점에 바로 할 수 있다.
C++03 C++11
57. 객체의 생성
C++11에 추가된 생성자 위임(Delegating Constructor)은
생성자 안에서 같은 클래스의 다른 생성자를 호출하는 것을 말한다.
단, 생성자 바디에서 다른 생성자를 부를 수는 없고 생성자 초기화 리스트를 이용해야 한다.
double 타입 생성자 실행1
string 타입 생성자의 바디 실행2
58. 객체의 생성
생성자를 위임할 때는 재귀적으로 꼬리를 무는 상황은 피해야 한다.
이 코드가 어떻게 동작할지는 C++ 표준에 정해진 것이 없으므로 전적으로 컴파일러에 달려있다.
59. 객체의 소멸
객체가 소멸할 때는 두 가지 작업이 발생한다.
먼저 객체의 소멸자가 호출되고 난 후, 할당받은 메모리가 반환된다.
소멸자를 정의하지 않으면 컴파일러가 자동으로 생성해준다.
자동으로 생성된 소멸자는 클래스 내 멤버들을 돌아가며
재귀적으로 소멸자를 호출하여 객체가 삭제될 수 있도록 한다.
anotherCell 객체는 이 블록이 끝나는 시점에 소멸한다.
myCell은 이 블록이 끝나는 시점에 소멸한다.
60. 객체의 소멸
스택에 생성된 객체는 유효 범위(Scope)에서 벗어날 때 자동으로 삭제된다.
즉, 해당 객체가 선언된 함수나 메서드 또는 중괄호 블록을 벗어나는 순간 소멸한다.
또한 스택에 생성된 객체는 생성 순서의 역순으로 삭제된다.
생성 순서 소멸 순서
61. 객체의 소멸
힙에 생성된 객체는 자동으로 소멸되지 않는다.
명시적으로 delete 연산자를 호출하면서 객체의 포인터를 인자로 넘겨야만
소멸자가 호출되고 메모리에서 해제된다.
cellPtr1을 삭제한다.
cellPtr2는 delete가 호출되지 않았기 때문에 삭제되지 않는다.
62. 객체의 대입
C++에서는 객체가 생성되면서 값이 초기화될 때만 복제가 일어날 수 있다.
이미 메모리를 할당받은 객체에 대해 값이 덮어써지는 상황은 대입이 더 적합한 표현이다.
C++에서는 복제 생성자만이 객체를 복제할 수 있고, 복제 생성자는 객체 생성에만 호출 될 수 있다.
C++에서는 객체 간 대입을 지원하는 대입 연산자(Assignment Operator)라는 별도의 방법을 제공한다.
63. 객체의 대입
복제 생성자와 달리 대입 연산자는 SpreadsheetCell의 참조 객체를 리턴한다.
그 이유는 대입 연산이 중첩돼서 수행될 수 있기 때문이다.
=
myCell.operator=이 실행되려면 anotherCell.operator=이 반드시 리턴값을 줘야만 한다.
만약 리턴 타입을 SpreadsheetCell&이 아닌
SpreadsheetCell로 하면 임시 객체로 인한 복제 오버헤드 발생!
64. 객체의 대입
대입 연산자를 구현할 때 자기 자신을 인자로 하는 경우를 배제해서는 안된다.
단, 대입 작업을 그대로 수행하는 것도 문제이다.
대입 연산자가 실행되면 가장 먼저 인자가 자기 자신인지 검사하고
만약 그렇다면 복제 작업을 하지 말고 그대로 리턴하게 만들어야 한다.
65. 객체의 대입
컴파일러가 자동으로 생성하는 대입 연산자는 default와 delete 키워드를 이용해
명시적으로 사용을 선언할 수도 있고, 배제할 수도 있다.
66. 객체의 대입
어떤 때는 객체가 복제 생성자에 의해 초기화되는 건지
대입 연산자가 실행되어 값이 대입되는 건지 모호한 경우가 있다.
기본적으로 변수 선언을 할 때는 복제 생성자가 이용되고
대입 연산을 수행할 때는 대입 연산자가 호출된다.
anotherCell은 대입? 복제?
복제!
aThirdCell은 대입? 복제?
복제!
대입? 복제?
대입!
67. 객체의 대입
함수나 메서드에서 객체를 리턴할 때
복제가 일어날지 대입이 일어날지 정확히 알 수 없는 때도 있다.
getString() 함수 호출
string 임시 객체 생성 / 복제 생성자 호출
s1의 대입 연산자 호출 / 임시 객체 소멸
1
2
3
getString() 함수 호출1
string 임시 객체 생성 / 복제 생성자 호출2
s2의 복제 생성자 호출 / 임시 객체 소멸3
68. 객체의 대입
컴파일러가 자동으로 생성하는 복제 생성자에서는 멤버들의 복제 생성자를 재귀적으로 호출한다.
직접 복제 생성자를 만들 때는 컴파일러가 생성한 복제 생성자와 마찬가지로
생성자 초기화 리스트에서 멤버의 복제 생성자를 호출할 수 있다.
이때 만약 멤버 초기화를 빠뜨리면 컴파일러가 빠뜨린 멤버에 대해서 디폴트 생성자를 부른다.
이 디폴트 생성자는 직접 작성한 복제 생성자의 바디가 실행되기 전에 수행된다.
이 때문에 생성자 바디가 실행되는 시점에는 이미 모든 멤버들이 초기화된 상태가 된다.
복제 생성자 호출
대입 연산자 호출