본문 바로가기

C++/FOCU_C++

C++ chpt7.

생성자(Constructor)

// JAVA
public class Vector
{
    private int x;
    private int y;
    // 기본 0으로 자동 초기화

    // 매개변수 없는 생성자
    // 안만들어도 상관없음
    public Vector() // 위의 멤버 변수에 값을 지정하는 것과 같아서 필요성이 적음
    {
         x = 0;
         y = 0;
    }
}

// JAVA 는 라이브러리를 만들어서 배포하는 할 때, cpp 와 헤더 파일이 분리 되어있지 않음
// 패키지를 사용하는 사람들은 해당 소스코드를 볼 방법이 없음(publice, private 모두)
// 또한 제작자는 위에서 부터 데이터를 보면서 무슨 데이터가 있는지를 볼 수 있기 때문에
// private 를 우선적으로 명시하는 습관을 가졌을거라 유추
-------------------------------------------------------------
// C++
class Vector
{
public:
    // 매개변수 없는 생성자
    Vector()
    {
        mX = 0;
        mY = 0;
    }
private:
    int mX;
    int mY;
}

// C++ 는 라이브러리를 사용하는 사람에겐 private 의 접근권한이 없으니 의미가 없음
// 따라서 접근 가능한 public 을 우선적으로 명시하는 슴관을 가졌을거라 유추

 

 

 

C++ 의 초기화 리스트

// 대입

class Vector
{
public:
    Vector()
    {
        mX = 0;
        mY = 0;
    }
private:
    int mX;
    int mY;
}

-----------------------------------------------

// 초기화리스트

class Vector
{
public:
    Vector()
        : mX(0)
        , mY(0)
    {
    }
    
private:
    int mX;
    int mY;
}

 

: 시점에서 초기화리스트 영역으로 멤버 변수의 값을 초기화할 수 있음

 

멤버 변수를 대입없이 초기화를 하며, 상수나 참조 변수도 초기화가 가능함

 

생성자 본문에서의 대입연산 초기화에선 다음과 같은 차이를 볼 수 있다

class X
{
    const int nNameSize;
    AnotherClass& mAnother;

    X(AnotherClass& another)
    {
        mNameSize = 20;		// 에러
        mAnother = another;	// 에러
    }
};

-------------------------------------------------------

class X
{
    const int nNameSize;
    AnotehrClass& mAnother;

    X(AnotherClass& another)
        : mNameSize(20)
        , mAnother(another)
    {
    }
};

 

const 와 참조 변수는 초기화시 값을 정해줘야 되기 때문에 생성자 본문에서의 대입 연산을 적용되지 않는다.

 

 

여기서 위의 private  의 차선 명시와 함께 멤버 변수 초기화시 중요한 부분이 멤버 변수 선언이 후에 발생하는 만큼, 

멤버 변수의 의존 관계가 있는 멤버 변수 끼리의 사용이나 초기화 측면에서 순서를 잘 지켜야함.

class BadInit {
public:
    BadInit() : b(a), a(5) {}
private:
    int a;
    int b;
};

이 코드의 경우 b(a) 를 먼저 실행하려고 하지만, a 는 아직 초기화되지 않았기 때문에 미정의 동작이 발생 할 수 있음

 

그렇기 때문에 a(5) 를 먼저 초기화 시킨 후 b(a) 를 하겠끔, 멤버 변수가 명시된 순서대로 초기화를 적용시켜야 함

 

또한 다음 코드처럼

class A {
public:
    A() {
        x = 5;  //  멤버 x가 선언되지 않았다면 오류
    }

private:
    int x;
};
클래스 벡터 { 공공의: 벡터() { MX = 0; 내 = 0; } 사적인: int mx; int my; } ------------------------------------------------------- 클래스 벡터 { 공공의: 벡터() : MX (0) , 내 (0) { } 사적인: int mx; int my; }
 

초기화가 아닌 생성자 본문에서 대입을 통해 값을 전달하는 경우이기 때문에 이미 기본 생성자를 통해 초기화된 후 대입이 일어나는 비효울이 발생할 수 있음

 

 

여기서 조금 의문이 드는 게 초기화리스트도 어찌됐든 변수 선언과 별개의 코드로 값을 넣어주는 동작을 명시한건데 이게 어떻게 대입 연산과 다르다는 걸까? 하는 생각이 들 수 있음

 

우리는 보통 초기화란 작업을 '메모리 정의와 동시에 값을 넣어주는 행위' 로 인지하고 있기 때문에

 

그래서 대입연산(=) 도 i
nt a = 5 는 초기화
int a; a = 5 는 대입연산

이라고 염연히 구분을 짓기 때문에 더 혼란스러울 수 있음

 

이건 생성자 호출 시점을 기준으로 보면 명확하게 나줘져있음을 알 수 있음

 

객체는 생성시 생성자 호출 전에 객체의 레이아웃을 구성하면서

 

멤버 변수의 선언과 메모리 정의도 같이 진행

 

여기에 더해서 초기화리스트까지 작업까지 동시에 이뤄지고

 

생성자 본문이 실행되면서 코드의 실행부가 실행된다고 이해하면 된다.

 

 

 

조금 더 C++ 스러운 클래스 만들기

 

Cpp 에서 멤버 변수의 값을 0 으로 초기화 시켜주는게 C++98 에선 보편적으로 지향하는 설정이다

 

SetX() 같은 함수를 통해 멤버 변수의 값을 확실하게 설정 해준다면 굳이 설정하지 않아도 되지만,

 

그렇지 않다면 임의의 초기값을 넣어줌으로써, 정의되지 않은 값과 같은 에러로부터 안전하게 지킬 수 있다.

 

 

코드로 보는 C++

// Vector 클래스 Vector1.h

#pragma once

class Vector1
{
public:
    Vector1();
    Vector1(int x, int y);

private:
    int mX;
    int mY;
};
// Vector1.cpp

#include <iostream>
#include "Vector1.h"

using namespace std;

Vector1::Vector1()
    : mX(0)
    , mY(0)
{
    cout << "Vector1(): (" << mX << mY ", " << mY << "0" << endl;
}

Vector1::Vector1(int x, int y)
    : mX(x)
    , mY(y)
{
    cout << "Vector1(int x, int y): (" << mX << ", " << mY << ")" << endl;
}

 

 

기본생성자

  • 매개변수를 받지 않음
  • 클래스에 생성자가 없으면 컴파일러가 기본 생성자를 자동적으로 만들어줌
  • 자동적으로 만들어진 생성자는
    • 멤버 변수를 초기화하지 않음
    • 반면에 모든 포함된 개체의 생성자를 호출

컴파일러가 하는 일

생성자가 하나도 없을 경우 기본 생성자를 생성

 

 

생성자가 존재하면 기본 생성자는 생성하지 않음

 

 

생성자 오버로딩

  • 여러개의 생성자를 만들 수 있음
    • 같은 이름
    • 인자의 개수나 자료형은 다름

 

소멸자(Destructor)

// JAVA

public class Vector
{
    private int x;
    private int y;

    // 소멸자 없음
}

---------------------------------------------------

// C++
// Vector.h

class Vector
{
public:
    ~Vector(); // 소멸자
private:
    int mX;
    int mY;
};

// Vector.cpp
Vector::~Vector() // 소멸자 정의
{
}

 

  • 개체가 지워질 때 호출됨
  • 가상 소멸자
  • 스코프를 벗어날 때 자동으로 호출
  • JAVA
    • 자동으로 garbage 를 수집하기 때문에 소멸자 없음
  • C++
    • C++ 클래스는 그 안에서 동적으로 메모리를 할당할 수도 있음
    • 그런 경우 필히 소멸자에서 메모리를 직접 해제해 줘야 함

클래스 안에서의 동적 메모리 할당 예시

// MyString.h

class MyString
{
public:
    MyString();
private:
    char* mChars;
    int mLength;
    int mCapacity;
};

----------------------------------------------------------------------

// MyString.cpp

MyString::MyString()
    : mLength(0)
    , mCapacity(15)
{
    mChars = new char[mCapacity + 1];
}

-----------------------------------------------------------------------

// main.cpp

void Foo()
{
    Mystring name;
    // ...
}

 

객체의 생성과 해제 과정에서의 메모리 상태

객체가 사라지면 힙 영역 할당시 사용하던 메모리의 주소 정리가 되지 않는 상황이 발생

 

이와 같은 문제를 해결하기 위해서 소멸자가 존재

// MyString.h

class MyString
{
public:
    MyString();
    ~MyString(); // 힙 메모리 등을 정리
private:
    char* mChars;
    int mLength;
    int mCapacity;
};

----------------------------------------------------------------------

// MyString.cpp

MyString::MyString()
    : mLength(0)
    , mCapacity(15)
{
    mChars = new char[mCapacity + 1];
}

MyString::~Mysrint()
{
    delete[] mChars; // 소멸자 호출시 delete 동작

    // 객체가 사라지기 때문에 초기화 할 필요가 없음
    // mCapacity = 0;
    // mChars = NULL;
}

 

 

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

C++ chpt9.  (4) 2025.07.24
C++ chpt8.  (1) 2025.07.21
C++ chpt6.  (1) 2025.05.16
C++ chpt5.  (0) 2025.05.04
C++ chpt4.  (0) 2025.05.03