생성자와 관련된 내용들 정리
C++/C++ 2021. 6. 15. 20:14 |1. 사용자 정의 생성자가 있으면, 기본 생성자를 자동 제공하지 않는다
- 이는 클래스의 인스턴스가 묵시적으로 기본 값으로 초기화 되는 것을 방지하기 위함이다.
따라서, 기본 생성자가 제공되지 않는 경우에는 인자 없이 객체를 생성하려고 할 때 오류가 발생한다. - 그러나 복사 생성자와, 복사 할당자는 기본적으로 여전히 생성된다.
다만 특정 조건 하에서는 기본 복사 생성자와 복사 할당 연산자가 생성되지 않을 수 있다.
이동 생성자 또는 이동 할당 연산자가 사용자 정의된 경우(2번 참고) - std::vector와 같은 표준 컨테이너는 요소의 복사 생성자를 호출하여 요소를 복사한다.
만약 복사 생성자가 사용할 수 없거나 삭제된 경우에만 오류가 발생합니다.
2. 이동 생성자 or 이동 할당 연산자가 명시적으로 선언된 경우
컴파일러는 복사 생성자를 자동으로 정의하지 않는다.
- 이동 할당(Move Assignment) 연산자의 명시적 선언 시 → 컴파일러가 복사 할당 (Copy Assignment) 연산자를 자동으로 정의하지 않는다.
이는 클래스에 사용자 정의 이동 할당 연산자가 필요한 경우,
기본 복사 할당 연산자가 적절하지 않을 수 있음을 의미합니다.
3. 복사 생성자, 복사 할당 연산자, 이동 생성자, 이동 할당 연산자 또는 소멸자 중 하나라도 명시적으로 선언되면, 컴파일러는 기본 이동 생성자와 이동 할당 연산자를 자동으로 생성하지 않는다.
- 이는 클래스가 자원을 관리하고 있을 경우,
기본적으로 이동 세맨틱스 수행되는 것이 적절하지 않을 수 있음을 가정하기 때문이다.
4. 컴파일러에 의해 암시적으로 생성된 default 복사 생성자는
얕은 복사(Shallow Copy)로 동작한다.
- 포인터만 복사하기 때문에 같은 대상을 가리키는 인스턴스가 생성되어 버린다.
따라서 깊은 복사(Deep Copy)를 원할 경우 복사 생성자를 직접 정의해 주어야한다.
5. explicit 키워드를 붙이면 객체 생성 시
컴파일러가 자동으로 형변환을 시도하지 않는다.
struct A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
operator bool() const { return true; }
};
struct B
{
explicit B(int) { }
explicit B(int, int) { }
explicit operator bool() const { return true; }
};
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
if (a1) ; // OK: A::operator bool()
bool na1 = a1; // OK: copy-initialization selects A::operator bool()
bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
if (b2) ; // OK: B::operator bool()
// bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}
6. 최상위 객체의 소멸자에 virtual 키워드를 붙이지 않으면
하위 객체의 소멸자가 호출되지 않는다.
아래 코드는 상속 관계에 있는 객체 A, B, C를 다형성을 이용하여
하나의 컨테이너 std::list<A*> 에서 관리하는 코드이다.
class A
{
public:
A() = delete;
A(int _value) :a(new int(_value))
{
cout << "A+(" << *a << ")\n";
}
A(const A& _a) :a(new int(*_a.a))
{
cout << "A+ Copy(" << *a << ")\n";
}
virtual void Print() const { cout << "A::Print()" << *a << '\n'; }
virtual ~A()
{
cout << "A-(" << *a << ")\n";
delete a;
}
int* a;
};
class B :public A
{
public:
B() = delete;
B(int _value) :A(_value)
{
b = new int(_value);
cout << "B+(" << _value << ")\n";
}
B(const B& _b) :A(*this), b(new int(*_b.b))
{
cout << "B+ Copy(" << *b << ")\n";
}
void Print() const
{
cout << "B::Print()" << *b << '\n';
A::Print();
}
~B()
{
cout << "B-(" << *b << ")\n";
delete b;
}
int* b;
};
class C : public B
{
public:
C() = delete;
C(int _value) :B(_value)
{
c = new int(_value);
cout << "C+(" << _value << ")\n";
}
C(const C& _c) :B(*this), c(new int(*_c.c))
{
cout << "C+ Copy(" << *c << ")\n";
}
void Print() const
{
cout << "C::Print()" << *c << '\n';
B::Print();
}
~C()
{
cout << "C-(" << *c << ")\n";
delete c;
}
int* c;
};
void Print(std::list<A*> list);
void Delete(std::list<A*> list);
int main()
{
std::list<A*> list;
cout << "========== Step 1 ==========\n";
list.push_back(new A(1));
cout << "========== Step 2 ==========\n";
list.push_back(new B(2));
cout << "========== Step 3 ==========\n";
list.push_back(new C(3));
Print(list);
Delete(list);
}
void Print(std::list<A*> list)
{
cout << "\n========== Print ==========\n";
for (auto it = list.begin(); it != list.cend(); it++)
{
(*it)->Print();
cout << "\n";
}
cout << "\n\n";
}
void Delete(std::list<A*> list)
{
for (auto it = list.begin(); it != list.cend(); it++)
{
cout << "delete\n";
delete* it;
cout << "\n";
}
}
위 코드를 실행하면 아래와 같은 결과가 나온다.
그런데 만약 최상위 객체 A의 소멸자에 virtual 키워드가 붙어있지 않으면 어떻게 될까?
생성자는 최하위 객체에서 부터 호출되지만, 소멸자는 최상위 객체에서 부터 호출된다.
따라서 최상위 객체의 소멸자에 virtual 키워드를 붙이지 않으면
최상위 객체의 소멸자만 호출되고 끝나버리기 때문에 하위 객체의 리소스가 해제되지 않는다.
그 결과 메모리 누수가 발생하게 된다.
'C++ > C++' 카테고리의 다른 글
Effective C++ 상속된 클래스가 있는 경우 가상 소멸자를 사용 (0) | 2021.06.27 |
---|---|
this_thread::sleep_for(0) 와 this_thread::yield() 의 차이점 (0) | 2021.06.27 |
C++ 접근 지정자(Access specifiers) (0) | 2021.06.13 |
Variable-Length Array 배열 매개변수로 넘기는 쉬운 방법 (0) | 2021.06.12 |
템플릿(template) 사용 시 typename 사용 법 (0) | 2021.06.07 |