Skip to content

Latest commit

 

History

History
768 lines (617 loc) · 26.4 KB

8_클래스와_객체_숙달하기.md

File metadata and controls

768 lines (617 loc) · 26.4 KB

목차


소개

  • 이번 장에서는 클래스의 정의, 메서드 정의, 스택과 힙에서 객체를 생성해서 사용하는 방법, 생성자 작성법, 디폴트 생성자, 컴파일러가 생성하는 생성자, 생성자 이니셜라이저, 복제 생성자, 이니셜라이저 리스트 생성자, 소멸자, 대입 연산자 등 클래스와 객체에 관련된 핵심 개념을 알아보자.

배울 내용

  • 나만의 클래스 작성법
  • 메서드와 데이터 멤버에 대한 접근 권한 지정법
  • 스택이나 힙에 객체를 생성하는 방법
  • 객체의 라이프 사이클
  • 객체가 생성될 때 실행할 코드 작성법
  • 객체를 복사 또는 대입하는 코드 작성법

8.1 스프레드시트 예제

  • 간단한 스프레드시트 예제로 여러 개념을 설명할 예정
  • SpreadsheetCell이라는 기본 클래스 중심으로 소개

8.2 클래스 작성 방법

  • 객체에 적용할 동작(메서드)과 객체마다 가질 속성(데이터 맴버) 지정
  • 클래스를 정의하는 단계와 메서드를 정의하는 단계로 구성

8.2.1 클래스 정의

  • SpreadsheetCell 클래스의 첫 버전
class SpreadsheetCell {
  public:
    // SpreadsheetCell 클래스에서 지원하는 메서드 선언
    void setValue(double inValue);
    double getValue() const;

  private:
    // 클래스의 데이터 멤버 선언
    double mValue;
};
  • 클래스 정의는 문장 이므로 끝날 때, 세미콜론(;)을 빼먹지 말자!
  • 클래스 정의를 작성한 파일은 보통 클래스 이름.h 로 짓는다.

1. 클래스 멤버

  • 클래스는 여러 멤버를 갖는다. 멤버는 멤버 함수와 멤버 변수(데이터 멤버) 로 구성
  • 멤버 함수 : 메서드 생성자 소멸자
  • 멤버 변수(=데이터 멤버) : 열거형, 타입 앨리어스, 중첩 클래스 등..

2. 접근 제어

  • 각 클래스의 멤버들은 세 가지 접근 제한자를 갖는다.
  • 클래스에 접근 제한자를 지정하지 않는다면 private가 적용
class SpreadsheetCell {
  void setValuedouble inValue); // private
  public:
    double getValue() const;
  private:
    double mValue;
};
  • struct도 마찬가지로 class처럼 메서드를 가질 수 있음.
  • 하지만 메서드가 없거나 개수가 적다면(x, y좌표계) class보다 struct 구조를 사용
// struct ver
struct SpreadsheetCell {

    void setValue(double inValue);
    double getValue() const;

  private:
    double mValue;
};

3. 선언 순서

  • 선언 순서는 따로 지정한게 없다. 편한대로 하자.
  • 가독성을 위해서 public, protected, private로 묶어서 선언하도록 하자!

4. 클래스 내부의 멤버 이니셜라이저

  • 클래스를 정의할 때는 멤버 변수를 선언하는 동시에 초기화가 가능
class SpreadsheetCell{
  // 클래스 정의의 나머지 부분 생략
  private:
    double mValue = 0; // 기본값 0으로 초기화
};

8.2.2 메서드 정의 방법

  • 함수를 만들 때는 프로토타입 뿐만 아니라 함수를 구현하는 정의 코드를 함께 작성하듯이
  • 메서드도 프로토타입 뿐만 아니라 메서드를 구현하는 정의 코드도 반드시 작성!
  • 메서드 정의 코드보다 클래스 정의 코드가 먼저 나와야 함!
#include "SpreadsheetCell.h"

// 메서드 이름 앞에 클래스 이름과 콜론 두 개가 붙은 것을 잘 보자
// :: 는 스코프 지정 연산자 라고 함. 즉, setValue, getValue는 SpreadsheetCell 클래스에 속함
void SpreadsheetCell::setValue(double inValue) {
  mValue = inValue;
}

double SpreadsheetCell::getValue() const {
  return mValue;
}

1. 데이터 멤버 접근 방법

  • setValue() or getValue()는 클래스에 정의된 데이터 멤버 중 현재 객체에 속한 멤버에 대해 접근
  • setValue() 메서드를 호출 -> mValue 변수 값을 변경함

2. 다른 메서드 호출하기

  • SpreadsheetCell 클래스에 텍스트 데이터를 지원하도록 수정한 첫 번째 버전
#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);
}

3. this 포인터

  • 일반 메서드를 호출하면 항상 메서드가 속한 객체의 포인터인 this가 숨겨진 매개변수 형태로 전달
  • this 포인터로 해당 객체의 데이터 멤버나 메서드에 접근 가능
  • 다른 메서드나 함수에 매개변수로 전달 가능
  • 이름을 명확히 구분하는 용도
void SpreadsheetCell::setValue(double value){
  value = value; // 모호한 표현
}

// this 포인터 사용
void SpreadsheetCell::setValue(double value){
  this.value = value; // 명확히 구분
}

8.2.3 객체 사용법

  • 객체를 생성해서 사용하는 방법에 대해서 알아보자. 크게 두가지가 있다.

    1. 스택에 생성하는 방법
    2. 힙에 생성하는 방법

1. 스택에 생성한 객체

// 객체를 스택에 생성한 예제
SpreadsheetCell myCell, anotherCell;
myCell.setValue(6);
anotherCell.setString("3.2");
cout << "cell 1: " << myCell.getValue() << endl;
cout << "cell 2: " << anotherCell.getValue() << endl;
  • 변수 타입이 클래스 이름이라는 것이 다름.
  • 데이터 멤버를 public으로 선언하는 것은 바람직하지 않다.

2. 힙에 생성한 객체

// new 를 사용하여 힙에 동적으로 생성한 예제
SpreadsheetCell* myCellp = new SpreadsheetCell();
myCellp->setValue(3.7);
cout << "cell 1: " << myCellp->getValue() << " " << myCellp->getString() << endl;
delete myCellp;
myCellp = nullptr;
  • 힙에 생성하면 -> 연산자를 통해 멤버에 접근
  • 이는역참조 연산자(*)와 멤버 접근 연산자(.)를 합친 것
  • 힙에 할당한 객체 메모리도 반드시 delete를 통해서 해제 필수
  • 반드시 아래와 같이 스마트 포인터를 사용하자!
auto myCellp = make_unique<SpreadsheetCell>();
myCellp->setValue(3.7);
cout << "cell 1: " << myCellp->getValue() << " " << myCellp->getString() << endl;
  • 스마트 포인터를 사용하면 메모리를 자동으로 해제하므로 delete를 사용할 필요가 없음.

8.3 객체의 라이프 사이클

  • 객체 라이프 사이클에 대해서 알아보자.
  • 생성 소멸 대입의 세 단계로 구성되어짐.
  • 동작을 원하는 방식으로 변경하는 방법도 알아두자.

8.3.1 객체 생성

#include <string>

class MyClass{
  private:
    std::string mName;
};

int main() {
  MyClass obj;
  return 0;
}
  • 객체도 선언과 동시에 초기값을 설정하는 것을 권장
  • 이 작업은 생성자 라고 부르는 특수한 메서드에서 객체를 초기화하는 코드를 작성하는 방식으로 처리할 수 있음.
  • 생성자를 간단히 ctor라고 부름.

1. 생성자 작성 방법

class SpreadsheetCell {
  public:
    SpreadsheetCell(double initialValue);
    // 나머지 부분 생략
};

// 구현 코드
SpreadsheetCell::SpreadsheetCell(double initialValue) {
  setValue(initialValue);
}
  • 생성자도 일반 메서드를 구현하듯 구현코드를 작성해야 함.
  • 생성자 이름은 클래스 이름과 똑같이 지정.
  • 리턴값 없으며 필요에 따라서 매개변수를 받음.
  • 특히 아무런 인수를 주지 않고 호출하는 생성자를 디폴트 생성자 라고 한다.
  • 생성자도 일종의 SpreadsheetCell 클래스 멤버이다! 따라서 지정 연산자를 붙여야 함.

2. 생성자 사용법

  • 객체는 생성자를 통해 생성이 됨.
  • 또한 그 객체의 값을 초기화 할 수 있다.
  • 스택과 힙 객체에 모두 생성자를 사용 가능

스택 객체 생성자

SpreadsheetCell myCell(5), anotherCell(4);
cout << "cell 1: " << myCell.getValue() << endl;
cout << "cell 2: " << anotherCell.getValue() << endl;
  • 이 때, SpreadsheetCell 생성자를 아래와 같이 선언과 동시에 호출하면 안 된다.
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;

3. 생성자 여러개 제공하기

  • 클래스에 여러 생성자를 만들 수도 있다.
  • 인수의 개수나 타입만 서로 다르게 정의
  • 이를 오버로딩 이라고 한다.
// 두 개의 생성자를 갖도록 수정한 예제
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;
  • 생성자가 여러개라면 한 생성자 안에서 또 다른 생성자를 호출할 수도 있다. (복잡...하다..)
  • string 타입을 인수로 받는 생성자에서 double 타입 인수를 받는 생성자를 호출하는 예제를 보자.
SpreadsheetCell::SpreadsheetCell(string_view initialValue){
  SpreadsheetCell(stringToDouble(initialValue));
}
  • 위의 예제는 의도한 대로 실행되지 않는다! 이는 위임 생성자를 이용하면 가능함.

4. 디폴트 생성자

  • 아무런 인자를 받지 않는 생성자.
  • 또 다른 말로 영인수(제로 인수) 생성자 라고 함.

디폴트 생성자가 필요한 경우

  • 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인 myCell 이란 이름의 함수를 선언함.
  • 두 번째 문장을 보고 함수 이름을 객체처럼 사용하는 실수를 저질렀다고 착각!
  • 따라서 스택 객체를 생성할 때는 디폴트 생성자 이름 뒤에 소괄호를 생략하자.

컴파일러에서 생성한 디폴트 생성자

  • SpreadsheetCell 클래스 정의의 첫 번째 버전에서 다음과 같이 코드를 작성해도 컴파일 에러는 발생하지 않는다.
SpreadsheetCell myCell;
myCell.setValue(6);
  • 아래의 예시는 디폴트 생성자를 직접 선언하지 않음
class SpreadsheetCell{
  public:
  SpreadsheetCell(double initialValue); // 디폴트 생성자가 없음
  // 생략
};

// 이렇게 작성한 뒤, 아래와 같이 작성시 컴파일 에러
SpreadsheetCell myCell;
myCell.setValue(6);
  • 생성자를 지정하지 않으면 컴파일러가 디폴트 생성자를 대신 만들어 주기 때문
  • 디폴트 생성자는 생성자를 하나도 선언하지 않아서 자동으로 생성되는 생성자라는 뜻도 있지만, 인수가 없어서 기본으로 호출되는 생성자라는 의미도 된다.

명시적 디폴트 생성자

  • 이를 사용하면 클래스 구현 코드에 디폴트 생성자를 작성하지 않아도 가능
class SpreadsheetCell {
  public:
    SpreadsheetCell() = default;
    SpreadsheetCell(double initialValue);
    SpreadsheetCell(std::string_view initialValue);
    // 생략
};

명시적으로 삭제된 생성자

  • 이 개념은 정적 메서드로만 구성된 클래스를 정의하면 생성자를 작성할 필요가 없을 뿐 아니라 컴파일러가 디폴트 생성자를 만들면 안된다.
  • 즉, 디폴트 생성자를 명시적으로 삭제해야 함.
class MyClass {
  public:
    MyClass() = delete;
};

5. 생성자 이니셜라이저

  • 지금까지는 데이터 멤버를 생성자 안에서 초기화 했음
  • 또 다른 방법인 생성자 이니셜라이저(멤버 이니셜라이저 리스트) 를 제공함.
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) { }
  • SomeClass 데이터 멤버인 mCell에 대해 디폴트 생성자가 없기 때문에 컴파일러는 mCell을 초기화 할 방법을 알 수 없음
  • 따라서 생성자 이니셜라이저를 작성해야 한다.

생성자 이니셜라이저는 객체를 생성하는 시점에 데이터 멤버를 초기화 한다.

  • 주의할 점
    • 클래스 정의에 작성한 순서대로 초기화 된다.
    • 따라서, 데이터 멤버는 생성자 이니셜라이저에 나온 순서가 아니라 클래스 정의에 나온 순서대로 초기화 한다.

6. 복제 생성자

  • 특수한 생성자로, 다른 객체와 똑같은 객체를 생성할 때 사용
// SpreadsheetCell의 복제 생성자
class SpreadsheetCell {
  public:
    SpreadsheetCell(const SpreadsheetCell& src);
    // 생략
};
  • 복제 생성자는 원본 객체에 대한 const 레퍼런스를 인수로 받음.
  • 다른 생성자와 마찬가지로 리턴값 없음

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

  • 함수나 메서드에 객체를 전달하면 컴파일러는 그 객체의 복제 생성자를 호출하는 방식으로 초기화 진행
// string 매개변수를 값으로 받는 printString ()함수
void printString(string inString) {
  cout << inString << endl;
}
  • string 매개변수인 inString은 이 클래스의 복제 생성자를 호출하는 방식으로 초기화 진행
string name = "hong gil dong";
printString(name); // name 복제
  • 복제 생성자에 const 레퍼런스로 매개변수를 전달하면 오버헤드를 줄일 수 있음.

복제 생성자 명시적 호출

  • 주로 다른 객체를 똑같이 복사하는 방식으로 객체를 만들 때 사용
SpreadsheetCell myCell1(4);
SpreadsheetCell myCell2(myCell1); // myCell2는 myCell1과 같음

레퍼런스로 객체 전달

  • 레퍼런스로 전달하면 복제 연산이 없으므로 오버헤드를 줄일 수 있음.
  • 단지 성능의 이유만으로 레퍼런스를 사용한다면 객체가 변경되어지지 않도록 const를 붙이자

7. 이니셜라이저 리스트 생성자

  • std::initializer_list를 첫 번째 매개변수
  • 다른 매개변수는 없거나 디폴트 값을 가진 매개변수를 추가로 받는 생성자
  • 다음 클래스는 짝수 개의 원소를 가진 initializer_list 만 매개변수로 받음
// 짝수 개수가 아니면 예외 발생
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"};
  • 즉, 생성자 뿐만 아니라 일반 함수에서도 사용이 가능!!

8. 위임 생성자

  • 같은 클래스의 다른 생성자를 생성자 안에서 호출할 수 있음.
SpreadsheetCell::SpreadsheetCell(string_view initialValue)
  : SpreadsheetCell(stringToDouble(initialValue))
{
}
  • string_view 타입 생성자가 호출
  • 이를 double 타입 생성자(타깃 생성자)에 위임
  • 타깃 생성자가 리턴하면 위임 생성자의 코드 실행
  • 재귀적으로 호출되지 않도록 주의

9. 컴파일러가 생성하는 생성자에 대한 정리

  • 컴파일러는 모든 클래스에 디폴트 생성자와 복제 생성자를 자동으로 만든다.
  • 따라서, 프로그래머가 직접 작성한 생성자에 따라 만들어주는 생성자가 달라질 수 있다!!

8.3.2 객체 소멸

  • 객체가 제거되는 두 단계 과정

    • 객체의 소멸자 호출 -> 할당받은 메모리 반환

    • 메서드 또는 코드 블록이 끝날 때

      • 즉, 스코프(유효 범위)를 벗어날 때 자동으로 삭제
  • 스마트 포인터를 사용하지 않는 힙 객체는 자동으로 삭제되어지지 않음

  • 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장에 설명되어 있음!

8.3.3 객체에 대입하기

  • 객체의 값을 다른 객체에 대입할 수 있다.
SpreadsheetCell myCell(5), anotherCell;
anotherCell = myCell;
  • 이미 값이 할당된 객체에 덮어쓸 때는 대입(assign) 이라고 표현
  • C++는 클래스마다 대입을 수행하는 메서드를 따로 제공 -> 대입 연산자
  • 위의 예제에서 anotherCell의 대입 연산자는 myCell이란 인수를 전달해서 호출
  • C++에서 객체끼리 서로 대입할 수 있도록 자동으로 만들어줌
  • 이를 디폴트 대입 연산자라고 하며 복제 동작과 거의 같음.
  • 즉, 원본 데이터 멤버를 대상 객체로 대입하는 작업을 재귀적으로 수행!

1. 대입 연산자 선언 방법

class SpreadsheetCell {
  public:
    SpreadsheetCell& operator=const SpreadsheetCell& rhs); // rhs(right-hand side) : 우항
    // 나머지 코드 생략
};
  • 대입 연산자는 복제 연산자와 달리 SpreadsheetCell 객체에 대한 레퍼런스를 리턴
  • 이유는 아래와 같이 여러 대입 연산이 연달아 나올 수 있기 때문
myCell = anotherCell = aThirdCell;
  • 쉽게 말해서 anotherCell에서 제대로 결과를 리턴하지 않으면 myCell로는 아무것도 전달되지 않음
  • 위 문장을 풀어쓰면..
myCell.operator=(anotherCell.operator=(aThirdCell));
  • 여기서 anotherCell을 직접 리턴하면 성능이 떨어지므로 anotherCell에 대한 레퍼런스를 리턴함

2. 대입 연산자 정의 방법

  • 복제 생성자와 대입 연산자의 차이점 2가지
    1. 복제 생성자는 초기화할 때 단 한 번만 호출
      • 자세한 내용은 9장에서
    2. C++은 객체에 자기 자신을 대입할 수 있음
      • 다음 예시에서 컴파일 오류가 발생하지 않음
SpreadsheetCell cell(4);
cell = cell; // 자기 자신 대입
SpreadsheetCell& SpreadsheetCell::operator=(const SpreadsheetCell& rhs)
{
  if(this == &rhs) { // 자기 자신을 대입하는지 확인
    return *this;
  }
}
  • this 키워드로 좌변 객체 지정, &rhs는 우변 객체 지정하는 포인터
  • 만약 두 포인터의 값이 같으면 자기 자신을 대입
  • this는 객체를 가리키는 포인터이므로 *this로 리턴

3. 명시적으로 디폴트로 만들거나 삭제한 대입 연산자

SpreadsheetCell& operator=(const SpreadsheetCell& rhs) = default; // 명시적으로 디폴트 생성

SpreadsheetCell& operator=(const SpreadsheetCell& rhs) = delete; // 명시적으로 삭제

8.3.4 컴파일러가 만들어주는 복제 생성자와 복제 대입 연산자

  • C++11 부터는 클래스에 사용자가 선언한 복제 대입 연산자 또는 소멸자가 있다면 복제 생성자를 생성해주는 기능을 지원하지 않는다.
// 굳이 사용한다면..
MyClass(const MyClass& src) = default;

8.3.5 복제와 대입 구분하기

  • 선언처럼 생겼다면 복제 생성자
  • 대입문처럼 생겼다면 대입 연산자
SpreadsheetCell myCell(5);
SpreadsheetCell anotherCell(myCell); // 복제 생성자

SpreadsheetCell aThirdCell = myCell; // 복제 생성자

//anotherCell의 operator= 호출하는 경우
anotherCell = myCell;

1. 리턴값이 객체인 경우

  • 함수나 메서드에서 객체를 리턴할 때 복제될지 아니면 대입될지 판단하기 힘들다.
string SpreadsheetCell::getString() const {
  return doubleToString(mValue);
}

// 호출
SpreadsheetCell myCell2(5);
string s1;
s1 = myCell2.getString(); // 이 한줄의 코드에서 복제 생성자와 대입 연산자가 서로 다른 두 객체에 대해서 호출되어짐.
  • 값을 리턴할 때 복제 생성자의 오버헤드가 크다면 리턴값 최적화 또는 복제 생략을 적용해서 초기화한다.

8.4 요약

  • 객체와 클래스의 개념을 확실히 짚고 넘어갈 수 있도록 하자.
  • 다음 장에서는 객체와 클래스에 관련된 보다 자세한 사항과 활용 기법에 대해서 알아보자.