전체를 다 보기에는 양이 많고, 아는 부분도 많아 필요한 부분만 정리하도록 하겠습니다.
▶ C 스타일 캐스팅과 정적 캐스팅
C 스타일 캐스팅을 활용하면, 포인터 사이의 크기는 서로 같기에 변환은 되지만, 관련 없는 데이터 타입으로 포인터를 캐스팅하는 경우가 발생할 수도 있습니다. 이를 정적 캐스팅인 static_cast<>를 사용하면 컴파일 에러를 발생시킬 수 있습니다. 정적 캐스팅하려는 포인터와 캐스팅 결과에 대한 포인터가 가리키는 객체가 서로 상속 관계에 있다면 컴파일 에러가 발생하지 않는데, 상속 관계에 있는 대상끼리 캐스팅할 때는 dynamic_cast<>인 동적 캐스팅을 사용하는 것이 더 안전합니다.
▶ wide string
wchar_t 타입은 유니코드 문자를 지원하는 문자 타입 중 하나로, 대체로 크기가 한 바이트인 char 타입보다 큽니다. string literal이 wide string이라는 것을 컴파일러에 알려주려면 그 앞에 L을 붙여야 합니다.
const wchar_t* myString = L"Hello, Wolrd";
▶ unique_ptr의 커스텀 삭제자
기본적으로 unique_ptr은 메모리 할당 및 해제를 new와 delete로 수행하는데, 다음과 같은 방식으로 변경할 수도 있습니다.
int *malloc_int(int _value) {
int* p = (int*)malloc(sizeof(int));
*p = value;
return p;
};
int main()
{
unique_ptr<int, decltype(free)*> myIntSmartPtr(malloc_int(42), free);
return 0;
}
malloc_int를 활용하여 메모리를 할당할 수 있도록 하며, 소멸자를 위하여 먼저 템플릿 인자로 함수의 타입을 decltype으로 확인한 후 이에 대한 포인터를 이용하여 함수 포인터를 구성하고, 함수 인자로 포인터를 넘겨주었습니다.
▶ shared_ptr - Aliasing
앨리어싱(aliasing)이란 한 shared_ptr이 한 포인터를 다른 shared_ptr과 공유하면서 다른 객체를 가리킬 수 있게 하는 기능입니다.
class Foo {
public:
Foo(int value) : mData(value) {};
int mData;
};
auto foo = make_shared<Foo>(42);
auto aliasing = make_shared<int>(foo, &foo->mData);
여기서 두 shared_ptr이 모두 삭제될 때만 Foo 객체가 삭제됩니다. 소유한 포인터에 대해서는 레퍼런스 카운팅에 사용하는 반면, 저장된 포인터는 포인터를 역참조 하거나 그 포인터에 대해 get 호출을 할 때 리턴됩니다. 저장된 포인터는 대부분의 연산에 적용할 수 있습니다.
▶ enable_shared_from_this
믹스인 클래스인 std::enable_shared_from_this를 이용하면 객체의 메서드에서 shared_ptr이나 weak_ptr을 안전하게 처리할 수 있습니다. enable_shared_from_this 믹스인 클래스는 다음 두 개의 메서드를 클래스에 제공합니다.
- shared_from_this() : 객체의 소유권을 공유하는 shared_ptr 리턴
- weak_from_this() : 객체의 소유권을 추적하는 weak_ptr 리턴 (C++17)
class Foo : public enable_shared_from_this<Foo>
{
public:
shared_ptr<Foo> getPointer() {
return shared_from_this();
}
};
int main() {
auto ptr1 = make_shared<Foo>();
auto ptr2 = ptr1->getPointer();
}
여기서 객체 포인터가 shared_ptr에 이미 저장된 상태에서만 객체에 shared_from_this()를 사용할 수 있다는 점에 주의해야 합니다. 예제의 main을 보면 make_shared로 Foo 인스턴스를 담은 shared_ptr인 ptr1을 생성했고, Foo 인스턴스에 대한 shared_from_this를 호출할 수 있게 되었습니다. getPointer()에서 shared_ptr를 리턴하면 중복 삭제가 발생합니다.
▶ 클래스 멤버 변수 생성 순서
생성자 이니셜라이저를 사용할 때 주의할 점으로, 나열한 데이터 멤버가 이니셜라이저에 나열한 순서가 아닌 클래스 정의에 작성한 순서대로 초기화 된다는 점입니다. 밑에 예시를 통해 알아보겠습니다.
class Foo {
public:
Foo(double value) : mValue(value)
{
cout << "Foo::mValue = " << mValue << endl;
}
private:
double mValue;
};
class MyClass1 {
public:
MyClass(double value) : mValue(value), mFoo(mValue)
{
cout << "MyClass1::mValue = " << mValue << endl;
}
private:
double mValue;
Foo mFoo;
};
class MyClass2 {
public:
MyClass(double value) : mValue(value), mFoo(mValue)
{
cout << "MyClass2::mValue = " << mValue << endl;
}
private:
Foo mFoo;
double mValue;
};
int main () {
MyClass1 a(1.2); // Foo::mValue = 1.2
// MyClass1::mValue = 1.2
MyClass2 b(1.2); // Foo::mValue = -9.25596e+61
// MyClass1::mValue = 1.2
};
위와 같이 private 내의 변수 위치를 바꾸는 것으로 생성의 순서가 바뀌어서 mFoo는 mValue를 통해 초기화를 하지만 mValue의 값이 정해지지 않았기에 이상한 값이 나옵니다. 따라서 클래스 정의에 나온 순서와 생성자 이니셜라이저에 나온 순서를 맞추는 것이 좋으며, 그렇지 않다면 컴파일러에서 경고 메세지를 출력할 수도 있습니다.
▶ 위임 생성자
위임 생성자를 사용하면 같은 클래스의 다른 생성자를 생성자 안에서 호출할 수 있습니다. 하지만 생성자 안에서 다른 생성자를 직접 호출할 수 없기에, 반드시 생성자 이니셜라이저에서 호출해야하며, 멤버 이니셜라이저 리스트에 이것만 적어야 합니다.
SpreadsheetCell::SpreadsheetCell(string_view initialValue)
SpreadsheetCell(stringToDouble(initialValue)) {};
▶ 컴파일러가 생성하는 생성자
복사 생성자는 명시적으로 정의하지 않는 한 컴파일러는 무조건 복제 생성자를 만들어 주며, 어떤 생성자라도(복사 생성자 포함) 정의했다면 컴파일러는 디폴트 생성자를 만들지 않습니다. 하지만 C++11부터 복제 대입 연산자 혹은 소멸자가 존재한다면 복제 생성자를 생성해주지 않고, 복제 생성자나 소멸자가 있으면 복제 대입 연사자를 생성해주지 않습니다.
▶ const 기반 오버로딩
const를 기준으로 오버로딩할 수 있어, const 객체에서는 const 메서드를, non-const 객체에서는 non-const 메서드를 실행합니다. 간혹 const 버전과 non-const 버전의 구현 코드가 똑같을 때가 존재하는데, 코드 중복을 위해서 const_cast 패턴이 사용 가능합니다.
class Spreadsheet
{
public:
SpreadsheetCell& getCellAt(size_t x);
const SpreadsheetCell& getCellAt(size_t x) const;
};
const SpreadsheetCell& Spreadsheet::getCellAt(size_t x) const
{ return mCells[x]; }
SpreadsheetCell& Spreadsheet::getCellAt(size_t x)
{ return const_cast<SpreadsheetCell&>(std::as_const(*this).getCellAt(x)); }
std::as_const 함수는 C++17부터 추가되어, 이전 버전에서는 static_cast가 활용 가능합니다.
▶ 명시적으로 오버로딩 제거하기
class MyClass
{
public:
void foo(int i);
void foo(double i) = delete;
};
다음과 같이 인자를 int로 받지만 double로 받는 경우 암시적 변환을 통해 코드가 수행되는 것을 막기 위해서 double로 인자를 받는 함수를 만들어 처리하되 이를 delete로 명시적으로 제거하여 컴파일 에러가 발생하도록 만듭니다.
▶ 인라인 코드
인라인 메서드를 호출하는 코드에서 이를 정의하는 코드에 접근해야만 컴파일러가 메서드 호출 부분을 본문에 나온 코드를 대체할 수 있기 때문에, 인라인 메서드는 반드시 프로토타입과 구현 코드를 헤더 파일에 작성합니다.
'C++공부 > 그 외의 C++' 카테고리의 다른 글
전문가를 위한 C++ : 13 ~ 15장 (0) | 2022.07.14 |
---|---|
전문가를 위한 C++ : 10 ~ 12장 (0) | 2022.07.13 |
전문가를 위한 C++ : 1 ~ 3장 (0) | 2022.07.11 |
Optimized C++ 9 - 13장 (0) | 2022.07.08 |
Optimized C++ : 4 ~ 8장 (0) | 2022.07.07 |