[모던C++입문] 2.3 생성자 및 할당 연산자
- 📕 Book/모던C++입문
- 2021. 7. 6.
2.3 생성자 및 할당 연산자
생성자
class complex
{
public:
//디폴트 생성자
complex(double rnew, double inew)
: r(), 1() //컴파일러가 생성
{
r = rnew, i = inew;
}
//멤버 초기화 리스트(Initialization List)를 이용한 생성자
//초기화 순서와 정의 순서가 일치하지 않으면 컴파일러가 경고를 출력
complex(double rnew, double inew) : r(rnew), i(inew) {}
private:
double r, i;
}
//생성자를 통한 값 설정
complex c1(2.0, 3.0);
class matrix_type
{
public:
matrix_type(int nrows, int ncols) {}
//후략
};
class solver
{
public:
solver(int nrows, int ncols)
// : A() 오류 : 존재하지 않는 디폴트 생성자 호출
{
A(nrows, ncols); //오류 : 생성자 안에서 다른 생성자를 호출할 수 없음
//A.operator()(nrows, ncols); 로 해석
}
private:
matrix_type A;
}
class complex
{
complex(double r, double i) : r(r), i(i) {}
complex(double r) : r(r), i(0) {}
complex() : r(0), i(0) {}
//위 3가지 생성자는 아래생성자로 대체 가능
complex(double r = 0, double i = 0) : r(r), i(i) {}
}
complex z1; //디폴트 생성
complex z2(); //디폴트 생성이 아니라 complex를 반환하는 함수 선언
complex z3(4); //z3(4.0, 0.0)
complex z4 = 4; //z4(4.0, 0.0)
- C++에는 다음 3가지 특수 생성자를 갖는다
- 디폴트 생성자
- 복사 생성자 (Copy Constructor)
- 이동 생성자 (Move Constructor) C++11
디폴트 생성자
- 인수가 없는 생성자 또는 모든 인수가 기본값을 갖는 생성자
- 가능하면 디폴트 생성자를 정의하자
복사 생성자
class complex
{
public:
complex(const complex& c) : i(c.i), r(c.r) {}
//복사 생성자의 인수는 값에의한 전달을 할수 없다 (무한루프에 빠질수 있음)
//complex(complex c) : i(c.i), r(c.r) {} //빌드 오류
//...
}
int main()
{
complex z1(3.0, 2.0),
z2(z1), //복사
z3{z1}; //C++11 축소하지 않음
}
- 복사생성자가 필요한 경우는 클래스에 포인터가 포함되어있는 경우이다
class vector
{
public:
vector(const vector& v)
: size(v.size), data(new double[size])
{
for(int i = 0; i < size; i++)
data[i] = v.data[i];
}
~vector() { delete[] data; }
private:
int size;
double* data;
//data의 타입은 원시 포인터보다는 unique_ptr을 사용하자
std::unique_ptr<double[]> data;
}
변환과 명시적 생성자
complex c1{3.0}; //C++11이상
complex c1(3.0); //모든 표준
complex c1 = 3.0;
double inline complex_abs(complex c)
{
return sqrt(c.r * c.r * c.i * c.i);
}
//complex_abs에 double 타입을 허용하는 함수 오버로드가 없지만
//complex에는 double타입을 허용하는 생성자가 있으므로
//double리터럴에서 complex값을 암시적으로 생성한다
cout << "|7| = " << complex_abs(7.0) << endl;
//다음과 같이 생성자를 explicit로 선언하면 암시적 생성을 비활성화 할 수 있다
explicit complex(double r = 0.0, double i = 0.0) : r(r) i(i) {}
//그러면 이제 명시적으로 complex를 생성해야한다
cout << "|7| = " << complex_abs(complex{7.0}) << endl;
위임 (C++11)
- c++11에서는 생성자 위임(Delegating constructor)을 제공
- 생성자에서 다른 생성자를 호출하는 기능
class complex
{
public:
complex(double r, double i) : r{r}, i{i} {}
complex(double r) : complex{r, 0.0} {}
complex() : complex{0.0, 0.0} {}
//...
}
멤버의 기본값 (C++11)
class complex
{
public:
complex(double r, double i) : r{r}, i{i} {}
complex(double r) : r{r} {}
complex() {}
private:
//다음과 같이 선언과 동시에 값을 입력하여 기본값을 설정
double r = 0.0, i = 0.0;
}
할당
- 개체 타입의 값을 할당하는 연산자를 복사 할당 연산자(copy assignment operator)
complex& operator=(const complex& src)
{
r = src.r, i = src.i;
return *this;
}
- 복사 할당 연산자는 복사 생성자와 유사하다
vector& operator=(const vector& src)
{
if(this == &src) //자기자신에게 할당하는 경우
return *this;
assert(my_size == src.my_size); //크기가 같은지 검사
for( int i = 0; i < mysize; i++)
data[i] = src.data[i];
return *this;
}
초기화 리스트(C++11)
float v[] = {1.0, 2.0, 3.0}; //일반적인 배열 초기화
//모든 클래스는 같은타입의 값 목록으로 초기화 가능
vector v = {1.0, 2.0, 3.0};
vector v{1.0, 2.0, 3.0};
유니폼 초기화(C++11)
- { } 중괄호는 변수 초기화를 위한 보편적인 표기법이다.
- 초기화 리스트 생성자
- 다른 생성자
- 직접 멤버 설정
- 모든 변수가 public이고 클래스에 사용자 정의의 생성자가 없는 경우 배열과 클래스에서만 가능하다 (집합 초기화 Aggregate Initialization)
- 집합 초기화 보다는 생성자를 사용하는 방식을 선호
struct sloppy_complex
{
double d, r;
}
sloppy_complex z1{3.66, 2.33},
z2 = {0, 1};
//유니폼 초기화의 인수로 초기화 리스트를 사용하고자 한다면 2중의 중괄호가 필요
vector v1 = {{1.0, 2.0, 3.0}},
v2{{3, 4, 5}};
//C++11에서는 중괄호 생략을 제공
vector v1 = {1.0, 2.0, 3.0},
v2{3, 4, 5};
//그러나 다음과 같은 현상이 발생할수 있다.
vector_complex v1d = {{2}}; //[(2,0)]
vector_complex v2d = {{2,3}}; //[(2,3)]
vector_complex v3d = {{2,3,4}}; //[(2,0),(3,0),(4,0)]
//3개의 인수를 같는 complex 생성자가 없기에 중괄호가 생략된 complex목록으로 인식
- 유니폼 초기화는 유용하지만 특수한 사례에서는 주의해야한다
이동 문법
- Lvalue : 표현식 이후에도 없어지지 않고 지속되는 객체
- Rvalue : 표현식 이후에는 더이상 존재하지 않는 임시적인 값
이동 생성자
- 이동 생성자는 원본에서 데이터를 훔처 빈 상태로 둔다
class vector
{
vector(vector&& v)
: my_size(v.my_size), data(v.data)
{
v.data = 0; //or nullptr(c+11)
v.mysize = 0;
}
}
이동 할당 연산자
class vector
{
vector& operator=(vector&& src)
{
assert(my_size == 0 || my_size == src.mysize);
std::swap(data, src.data);
return *this;
}
}
복사 생략 (Copy Elision)
- 복사 작업 대상 주소에 즉시 저장하도록 한다
inline vector ones(int n)
{
vector v(n);
// 초기화 생략
return v;
}
vector w(ones(7));
//컴파일러는 v를 생성하지 않고 w를 생성하여 w주소에서 연산을 수행
//복사(또는 이동) 생성자를 호출하지 않음
이동 문법이 필요할 때(C++11)
- 이동 생성자를 확실히 사용하려면 std::move 함수를 사용하자
- Lvalue를 Rvalue로 변환하여 이동 가능하게 한다
반응형