class SpreadsheetCell {
public:
// SpreadsheetCell 클래스에서 지원하는 메서드 선언
void setValue(double inValue);
double getValue() const;
private:
// 클래스의 데이터 멤버 선언
double mValue;
};
class SpreadsheetCell {
void setValuedouble inValue); // private
public:
double getValue() const;
private:
double mValue;
};
// struct ver
struct SpreadsheetCell {
void setValue(double inValue);
double getValue() const;
private:
double mValue;
};
class SpreadsheetCell{
// 클래스 정의의 나머지 부분 생략
private:
double mValue = 0; // 기본값 0으로 초기화
};
#include "SpreadsheetCell.h"
// 메서드 이름 앞에 클래스 이름과 콜론 두 개가 붙은 것을 잘 보자
// :: 는 스코프 지정 연산자 라고 함. 즉, setValue, getValue는 SpreadsheetCell 클래스에 속함
void SpreadsheetCell::setValue(double inValue) {
mValue = inValue;
}
double SpreadsheetCell::getValue() const {
return mValue;
}
#include <string>
#include <string_view>
class SpreadsheetCell{
public:
void setValue(double inValue);
double getValue() const;
void setString(std::string_view inString);
std::string getString() const;
private:
std::string doubleToString(double inValue) const;
double stringToDouble(std::string_view inString) const;
double mValue;
};
#include "SpreadsheetCell.h"
using namespace std;
void SpreadsheetCell::setValue(double inValue){
mValue = inValue;
}
double SpreadsheetCell::getValue() const{
return mValue;
}
void SpreadsheetCell::setString(string_view inString){
mValue = stringToDouble(inString);
}
string SpreadsheetCell::getString() const{
return doubleToString(mValue);
}
string SpreadsheetCell::doubleToString(double inValue) const {
return to_string(inValue);
}
double SpreadsheetCell::stringtoDouble(string_view inString) const {
return strtod(inString.data(), nullptr);
}
void SpreadsheetCell::setValue(double value){
value = value; // 모호한 표현
}
// this 포인터 사용
void SpreadsheetCell::setValue(double value){
this.value = value; // 명확히 구분
}
객체를 생성해서 사용하는 방법에 대해서 알아보자. 크게 두가지가 있다.
// 객체를 스택에 생성한 예제
SpreadsheetCell myCell, anotherCell;
myCell.setValue(6);
anotherCell.setString("3.2");
cout << "cell 1: " << myCell.getValue() << endl;
cout << "cell 2: " << anotherCell.getValue() << endl;
// new 를 사용하여 힙에 동적으로 생성한 예제
SpreadsheetCell* myCellp = new SpreadsheetCell();
myCellp->setValue(3.7);
cout << "cell 1: " << myCellp->getValue() << " " << myCellp->getString() << endl;
delete myCellp;
myCellp = nullptr;
auto myCellp = make_unique<SpreadsheetCell>();
myCellp->setValue(3.7);
cout << "cell 1: " << myCellp->getValue() << " " << myCellp->getString() << endl;
#include <string>
class MyClass{
private:
std::string mName;
};
int main() {
MyClass obj;
return 0;
}
class SpreadsheetCell {
public:
SpreadsheetCell(double initialValue);
// 나머지 부분 생략
};
// 구현 코드
SpreadsheetCell::SpreadsheetCell(double initialValue) {
setValue(initialValue);
}
SpreadsheetCell myCell(5), anotherCell(4);
cout << "cell 1: " << myCell.getValue() << endl;
cout << "cell 2: " << anotherCell.getValue() << endl;
SpreadsheetCell myCell.SpreadsheetCell(5); // 컴파일 에러!
// 마찬가지로 선언한 뒤 호출도 불가!
SpreadsheetCell myCell;
myCell.SpreadsheetCell(5); // 컴파일 에러!
auto smartCellp = make_unique<SpreadsheetCell>(4);
// ... 셀을 다룸 스마트 포인터이므로 직접 삭제하지 않아도 무방
// 일반 포인터도 사용 가능 but 권장하지는 않음
SpreadsheetCell* myCellp = new SpreadsheetCell(5);
SpreadsheetCell* anotherCellp = nullptr;
anotherCellp = new SpreadsheetCell(4);
// ... 셀을 다룸
// clean
delete myCellp;
myCellp = nullptr;
delete anotherCellp;
anotherCellp = nullptr;
// 두 개의 생성자를 갖도록 수정한 예제
class SpreadsheetCell {
public:
SpreadsheetCell(double initialValue);
SpreadsheetCell(std::string_view iitialValue);
// 생략..
};
// 두 번쨰 생성자의 구현 코드
SpreadsheetCell::SpreadsheetCell(string_view initialValue) {
setString(initialValue);
}
// 정의한 생성자를 사용하는 예제
SpreadsheetCell aThirdCell("Test"); // string 타입의 인수를 받는 생성자 사용
SpreadsheetCell aFourthCell(4.4); // double 타입의 인수를 받는 생성자 사용
auto aFifthCellp = make_unique<SpreadsheetCell>("5.5"); // string 타입의 인수를 받는 생성자 사용
cout << "aThirdCell: " << aThirdCell.getValue() << endl;
cout << "aFourthCell: " << aFourthCell.getValue() << endl;
cout << "aFifthCellp: " << aFifthCellp->getValue() << endl;
SpreadsheetCell::SpreadsheetCell(string_view initialValue){
SpreadsheetCell(stringToDouble(initialValue));
}
디폴트 생성자가 필요한 경우
- SpreadsheetCell 클래스에 디폴트 생성자를 정의하지 않으면 컴파일 에러 발생
SpreadsheetCell cells[3]; // 컴파일 오류! SpreadsheetCell* myCellp = new SpreadsheetCell[10]; // 여기서도 오류!
- 객체 배열을 생성할 때는 클래스에 디폴트 생성자를 정의하는 것이 편함!
- std::vector와 같은 라이브러리 컨테이너에 저장하려면 디폴트 생성자를 꼭 정의!
디폴트 생성자 작성법
class SpreadsheetCell { public: SpreadsheetCell(); // 생략 }; // 정의한 디폴트 생성자 구현 코드 SpreadsheetCell::SpreadsheetCell(){ mValue = 0; }
- 스택 객체의 디폴트 생성자 호출
SpreadsheetCell myCell; myCell.setValue(6); cout << "cell 1: " << myCell.getValue() << endl;
SpreadsheetCell myCell(); // 컴파일 에러 발생 X
myCell.setValue(6); // 이 문장에서 컴파일 에러 발생
cout << "cell 1: " << myCell.getValue() << endl;
컴파일러에서 생성한 디폴트 생성자
- SpreadsheetCell 클래스 정의의 첫 번째 버전에서 다음과 같이 코드를 작성해도 컴파일 에러는 발생하지 않는다.
SpreadsheetCell myCell; myCell.setValue(6);
- 아래의 예시는 디폴트 생성자를 직접 선언하지 않음
class SpreadsheetCell{ public: SpreadsheetCell(double initialValue); // 디폴트 생성자가 없음 // 생략 };
// 이렇게 작성한 뒤, 아래와 같이 작성시 컴파일 에러
SpreadsheetCell myCell;
myCell.setValue(6);
- 생성자를 지정하지 않으면 컴파일러가 디폴트 생성자를 대신 만들어 주기 때문
- 디폴트 생성자는 생성자를 하나도 선언하지 않아서 자동으로 생성되는 생성자라는 뜻도 있지만, 인수가 없어서 기본으로 호출되는 생성자라는 의미도 된다.
> 명시적 디폴트 생성자
- 이를 사용하면 클래스 구현 코드에 디폴트 생성자를 작성하지 않아도 가능
```cpp
class SpreadsheetCell {
public:
SpreadsheetCell() = default;
SpreadsheetCell(double initialValue);
SpreadsheetCell(std::string_view initialValue);
// 생략
};
명시적으로 삭제된 생성자
- 이 개념은 정적 메서드로만 구성된 클래스를 정의하면 생성자를 작성할 필요가 없을 뿐 아니라 컴파일러가 디폴트 생성자를 만들면 안된다.
- 즉, 디폴트 생성자를 명시적으로 삭제해야 함.
class MyClass {
public:
MyClass() = delete;
};
SpreadsheetCell::SpreadsheetCell(double initialValue)
: mValue(initialValue)
{
}
// 예를 들어 SpreadsheetCell를 다음과 같이 정의
class SpreadsheetCell {
public:
SpreadsheetCell (double d);
};
// 위의 클래스는 명시적 생성자만 있을 뿐, 디폴트 생성자는 없다.
// 아래의 클래스는 다른 클래스의 데이터 멤버로 정의하는 경우
class SomeClass {
public:
SomeClass();
private:
SpreadsheetCell mCell;
};
// SomeClass 구현 코드
SomeClass::SomeClass() { } // 컴파일 에러 발생
// 생성자 이니셜라이저 생성
SomeClass::SomeClass() : mCell(1.0) { }
생성자 이니셜라이저는 객체를 생성하는 시점에 데이터 멤버를 초기화 한다.
// SpreadsheetCell의 복제 생성자
class SpreadsheetCell {
public:
SpreadsheetCell(const SpreadsheetCell& src);
// 생략
};
복제 생성자가 호출되는 경우
- 함수나 메서드에 객체를 전달하면 컴파일러는 그 객체의 복제 생성자를 호출하는 방식으로 초기화 진행
// string 매개변수를 값으로 받는 printString ()함수
void printString(string inString) {
cout << inString << endl;
}
string name = "hong gil dong";
printString(name); // name 복제
복제 생성자 명시적 호출
- 주로 다른 객체를 똑같이 복사하는 방식으로 객체를 만들 때 사용
SpreadsheetCell myCell1(4);
SpreadsheetCell myCell2(myCell1); // myCell2는 myCell1과 같음
레퍼런스로 객체 전달
- 레퍼런스로 전달하면 복제 연산이 없으므로 오버헤드를 줄일 수 있음.
- 단지 성능의 이유만으로 레퍼런스를 사용한다면 객체가 변경되어지지 않도록 const를 붙이자
std::initializer_list<T>
를 첫 번째 매개변수initializer_list<T>
만 매개변수로 받음// 짝수 개수가 아니면 예외 발생
class EvenSequence {
public:
EvenSequence(initializer_list<double> args) {
if (args.size() % 2 != 0) {
throw invalid_argument("initializer_list should contain even number of elements.");
}
mSequence.reserve(args.size());
for (const auto& value : args) {
mSequence.push_back(value);
}
}
void dump() const {
for (const auto& value : mSequence) {
cout << value << ", ";
}
cout << endl;
}
private:
vector<double> mSequence;
};
std::vector<std::string> myVec = {"String 1", "String 2", "String 3"};
SpreadsheetCell::SpreadsheetCell(string_view initialValue)
: SpreadsheetCell(stringToDouble(initialValue))
{
}
객체가 제거되는 두 단계 과정
객체의 소멸자 호출 -> 할당받은 메모리 반환
메서드 또는 코드 블록이 끝날 때
스마트 포인터를 사용하지 않는 힙 객체는 자동으로 삭제되어지지 않음
delete를 사용해서 메모리를 해제하자
int main() {
SpreadsheetCell* cellPtr1 = new SpreadsheetCell(5);
SpreadsheetCell* cellPtr2 = new SpreadsheetCell(6);
cout << "cellPtr1: " << cellPtr1->getValue() << endl;
delete cellPtr1; // cellPtr1 삭제
cellPtr1 = nullptr;
return 0;
} // cellPtr2에 대해 delete를 직접 호출하지 않았으므로 삭제되어지지 않음
해제되지 않는 객체를 찾아주는 도구는 7장에 설명되어 있음!
SpreadsheetCell myCell(5), anotherCell;
anotherCell = myCell;
class SpreadsheetCell {
public:
SpreadsheetCell& operator=const SpreadsheetCell& rhs); // rhs(right-hand side) : 우항
// 나머지 코드 생략
};
myCell = anotherCell = aThirdCell;
myCell.operator=(anotherCell.operator=(aThirdCell));
SpreadsheetCell cell(4);
cell = cell; // 자기 자신 대입
SpreadsheetCell& SpreadsheetCell::operator=(const SpreadsheetCell& rhs)
{
if(this == &rhs) { // 자기 자신을 대입하는지 확인
return *this;
}
}
SpreadsheetCell& operator=(const SpreadsheetCell& rhs) = default; // 명시적으로 디폴트 생성
SpreadsheetCell& operator=(const SpreadsheetCell& rhs) = delete; // 명시적으로 삭제
// 굳이 사용한다면..
MyClass(const MyClass& src) = default;
SpreadsheetCell myCell(5);
SpreadsheetCell anotherCell(myCell); // 복제 생성자
SpreadsheetCell aThirdCell = myCell; // 복제 생성자
//anotherCell의 operator= 호출하는 경우
anotherCell = myCell;
string SpreadsheetCell::getString() const {
return doubleToString(mValue);
}
// 호출
SpreadsheetCell myCell2(5);
string s1;
s1 = myCell2.getString(); // 이 한줄의 코드에서 복제 생성자와 대입 연산자가 서로 다른 두 객체에 대해서 호출되어짐.