INDEX
- auto
- static_assert
- default/delete
- final
- override
- offsetof
- 새로운 자료형
- nullptr
- 고정 폭 소수형
- enum class
- 헤더에서 초기화하기
- 정리
auto
새로운 키워드들
- auto
- static_assert
- default/delete
- final/override
auto
- 자료형을 추론
- JavaScript등의 언어에 있는 동적인 형과는 다름
- 실제 자료형은 컴파일하는 동안 결정됨
- 반드시 auto변수를 초기화해야됨
auto로 포인터와 참조 받기
- auto를 사용하여 포인터와 참조를 받을 수 있음
- 포인터를 바을 때 : auto 또는 auto*
- 참조를 받을 때 : aut&
예시 : auto로 포인터 받기
Cat* myCat = new Cat("Coco", 2); auto myCatPtr = myCat; //myCat과 동일한 포인터일까요?? //네
왜 포인터와 값 둘 다 auto로 받을까?
-
컴파일러가 어느 형인지 알아낼 수 있음
auto a = 3; auto b = ptr; //ptr은 char*
-
그러나 이건 포인터가 아닐까?
auto name = object.GetName();
- 가독싱이 좋지 않음 -> 나쁜생각
-
포인터형을 받을 때는 auto*를 쓰자
auto* name = object.GetName();
-
즉 가독성을 위해 auto*를 쓰자!
예시 : auto로 참조 받기
Cat myCat("Coco", 2); Cat& myCatRef = myCat; auto anotherMyCatRef = myCatRef; //myCat에 대한 참조일까요?? //아니요
Cat myCat("Coco", 2); Cat& myCatRef = myCat; auto& anotherMyCatRef = myCatRef; //myCat에 대한 참조일까요?? //예
- auto& 여야 참조로 받는다
- 컴파일러는 구분을 못한다
예시 : auto로 const받기
const int b = 1; auto& a = b; //const를 이어받을까요? //예
왜 const와 비 const모두 auto로 받을 까?
-
다시 말하지만, 컴파일러가 알아낼 수 있기 때문
auto& a = constInt; //constInt는 const int auto& b = nonconstInt; //nonConstInt는 int
-
하지만 이건 const일까 아닐까?
auto& name = object.GetName();
- 위 코드는 가독성이 좋지 않음 0> 나쁘고 게으른 생각
-
가독성을 위해 const를 참조로 받을 때는
const auto&
를 쓰자conat auto& name = obejct.GetName();
auto와 함수 반환형
- auto 키워드는 함수가 반환하는걸 저장하는데 때론 유용
- 함수 반환형이 변해도 atuo는 그대로
- 허나 이런 일이 자주 발생할까?
auto는 엄청나게 좋다
- auto 키워드가 타이핑을 좀 줄여줌
- 허나 가독성을 떨어뜨림
- 아직 auto에 관해 모두가 동의하는 코딩 표준이 없음
auto와 반복자
-
반복자에는 auto키워드가 매우 유용
auto 사용 X
for(std::vector<int>::const_iterator it = v.begin()l it != v.end(); ++it) { //... }
auto 사용 O
for(auto it = v.begin(); i!= v.end(); ++it) { //... }
auto로 템플릿 형 받기
-
일부 템플릿의 경우에도 마찬가지
auto 사용 X
MyArray<int>* a = new MyArray<int>(10);
auto 사용 O
auto* a = new MyArray<int>(10);
베스트 프랙티스
- 명시적이어야 함
- auto보다 실제 자료형 사용을 권장
- 예외 : 템플릿 매개변수와 반복자에는 auto사용
- auto보다 auto*를 사용
- auto&보다 const auto&를 사용
- 전부 가독성과 관련된 것
- 런타임 성능은 동일
static_assert
static_assert
- 컴파일 도중에 assert가능
- 컴파일 도중에 어떤 조건을 확인 할 수 있으면 assert
assert
-
실행 중에 가정(assertion)이 맞는지 평가
-
오직 디버그 빌드에서만 작동
-
실패한 assert를 보려면 반드시 프로그램을 실행해야 함
- 모든 코드 경로가 실행되었다고 어떻게 장담할까?
- 일부 assertion은 프로그램을 실행하기도 전에 알 수 있음
- static_assert의 탄생 계기
-
모든 곳에 assert를 쓰자
- 여전히 유효한 조언
#include<cassert> const Student* Class:GetStudentInfo(const char* name) { assert(name != NULL);//조건이 false면 실행을 멈춤 //이하 코드 생략 }
static_assert
- 컴파일 중에 assertion 평가
- 컴파일러가 assert 조건이 참이니 아닌지 앎
- 실패하면 컴파일러는 컴파일 에러를 뱉음
- 많은 경우에 유용함
예시 : 구조체의 크기
Class.h
struct Student { char name[64]; char id[10]; int currentSemester; }; class class { public: //... const Student* GetStudentInfo(const char* name); };
Class.cpp
const Student* Class::GetStudentInfo(const char* name) { static_assertion(sizeof(Student) == 74, "Student struction size mismatch"); //이하 코드 생략 }
Result
예시 : version확인하기
Class.h
class Class { public: const static int Version = 1; // ... }
Main.cpp
#include"Class.h" static_assert(Class::Version>1, "You need higher version than 1.");
Result
예시 : 배열의 길이
Student.h
class Student { public: static const int MAX_SCORES = 10; int GetScores(int index); // ... private: int mScor es[MAX_SCORES]; };
Student.cpp
int Student::GetScores(int index) { static_assert(sizeof(mScores)/sizeof(mScores[0])==MAX_SCORES, "The Size of scores vector is not 10"); }
Result
베스트 프랙티스
- 최대한 많은 곳에 static_assert를 사용
- 첫째 모든 곳에 static assert를 쓰자
- 그러고 나면 모든 곳에 assert를 쓰자
- assert를 여전히 사용한다면
- 여전히 프로그램을 실행시켜야됨
- 실행중인 assertion을 포착, 그것도 오직 디버그 빌드에서만
- static_assert를 사용한다면
- 컴파일 중에 모든 문제를 즉시 알아챔
- 또한 컴파일러처럼 생각하는데도 도움이 됨
default/delete
default
- 컴파일러가 특정한 생성자, 연산자 및 소멸자를 만들어낼 수 있음
- 그래서, 비어있는 생성자나 소멸자를 구체화할 필요가 없음
- 또한 기본 생성자, 연산자 및 소멸자를 더 분명하게 표시할 수 있음
- 명확하게 표현하는 것은 항상 옳다
default사용 예시
default 사용 X
// Dog.h class Dog { public: Dog(); Dog(std::string name); private: std::string mName; };
//Dog.cpp #include "Dog.h" Dog::Dog() { } Dog::Dog(std::string name) { // 이하 코드 생략 }
defulat 사용 O
//Dog.h class Dog { public: Dog() = default; Dog(std::string name); private: std::string mName; };
//Dog.cpp #include "Dog.h" //컴파일러가 Dog()를 대신 만들어냄 Dog::Dog(std::string name) { // 이하 코드 생략 }
delete
- 컴파일러가 자동으로 생성자를 만들어주길 원치 않는다면 delete키워드를 사용
- private접근 제어자로 빈 생성자를 만드는 트릭은 이제 그만
- 올바른 에러 메시지도 나옴
delete 사용 예시
delete 사용 X
//Dog.h class Dog { public: Dog() = default; //... private: Dog(const Dog& other); //... }
//Dog.cpp #include "Dog.h" int main() { Dog myDog; Dog copiedMyDog(myDog); // 에러 // ... }
delete 사용 O
//Dog.h class Dog { public: Dog() = default; Dog(const Dog& other) = delete; //... }
//Dog.cpp #include "Dog.h" int main() { Dog myDog; Dog copiedMyDog(myDog); // 에러 // ... }
default/delete에 관한 베스트 프랙티스
- 컴파일러가 코드를 생성하는 암시적 방식에 기댈 필요가 ㅇ벗음
- 명확해야 함
- 어디에나 default/delete 키워드를 넣자
final
final
-
클래스나 가상 함수를 파생 클래스에서 오버라이딩 못하도록 할 때 사용
- 컴파일 도중 확인함. 매우 멋짐!
- 당연히 가상 함수가 아니면 쓸 수 없음
- 상속 안해주고 싶을 때 사용
class Animal final { //... }
class Dog final : public Animal { //... }
class Animal { public: //함수의 상속(재정의) 막기 virtual void SetWeight(float weight) final; //... }
override
override
- 잘못된 가상 함수 오버라이딩을 막으려면 override 키워드를 사용
- 부모 함수를 ㅐ정의 하겠다는 명시적 표시
- override하고 싶은데 실수로 인자를 잘못 써서 overload를 할 수도 있잖아>
- 부모 함수를 ㅐ정의 하겠다는 명시적 표시
- 당연히 가상 함수가 아니면 사용할 수 없음
- 이것도 컴파일 도중에 검사!
예시 : override 키워드
Animal.h
class Animal { public: virtual void SetWeight(float weight); void PrintAll(); // ... };
Dog.h
#include "Animal.h" class Dog : public Animal { public: //OK virtual void SetWeight(float weight) override; //컴파일 에러 virtual void SetWeight(int weight) override; //컴파일 에러 : 가상 함수가 아님 void PrintAll() override; // … }
offsetof
offsetof
-
매크로의 일종
-
특정 멤버가 본인을 포함한 자료 구조의 시작점에서부터 몇 바이트만큼 떨어져 있는지 알려줌
-
직렬화나 역직렬화할 때 꽤나 유용
#define offset(type, memeber) /*implementation-defined*/
예시 : 멤버들의 상대적 위치 구하기
struct Student { const char* ID; const char* Name; int CurrentSemester; }; int main() { std::cout << "ID offset : " << offsetof(Student, ID) << std::endl; std::cout << "Name offset : " << offsetof(Student, Name) << std::endl; std::cout << "CurrentSemester offset : " << offsetof(Student, CurrentSemester) << std::endl; return 0; }
Result
새로운 자료형
새로운 자료형들
- nullptr
- 고정 폭 정수형 (fixed-width integer types)
- enum class
nullptr
nullptr
-
NULL의 문제를 보안하기 위해 고안됨
-
NULL을 쓰면 뭔가 굉장히 이상한 일이 벌어짐
// Class.h float GetScore(const char* name); float GetScore(int id); // Main.cpp Class* myclass = new Class("COMP3100"); // … //어떤 함수가 호출될 지 알 수 없음 int scores = myClass->GetScore(NULL);
-
-
의미하는 것이 무엇일까?
- 포인터는 언제나 nullptr를 쓰자
- 더 이상 NULL이 있을 곳이 없음
NULL vs nullptr
NULL | nullptr |
---|---|
#define NULL | type decltype(nullptr) nullptr_t; |
숫자 | null 포인터 상수 |
int number = NULL; //OK int* ptr = NULL; //OK int anotherNumber = nullptr; //Error int* anotherPter = nullptr; //OK
예시 : nullptr 사용하기
// Main.cpp Class* myClass = new Class("COMP3200"); const Student* student = myClass ->GetStudent("Coco"); if(student != nullptr) { std::cout << studnet->GetID() << ":" << student->GetName() << std::endl; }
고정 폭 소수형
기본 자료형과 바이트 크기
- char : 1byte
- short : 2byte
- long : 4byte
- long long : 8byte
- int : 4byte
고정 폭 정수형
-
가독성을 위해 C++11에서 지원하는 정수형
-
int8_t / uint8_t
-
int16_t / uint16_t
-
int32_t / uint32_t
-
int64_t / uint64_t
-
intptr_t / uintptr_t
-
….
-
- http://en.cppreference.com/w/cpp/types/integer
-
가독성 향상을 위해 낡은 기존 자료형보다 이것들을 쓰자
` int8_t scores = student->GetScore(); `
enum class
enum class
- C++ 11에서 제대로 지원
- 정수형으로의 암시적 캐스팅이 없음
- 자료형 검사
- 또한 enum에 할당할 바이트 양을 정할 수도 있음
문제 : C-스타일 Enum
Main.cpp
enum eScoreType { Assignment1, Assignment2, Assignment3, Midterm, Count, }; enum eStudyType { Fulltime, Parttime, };
Main.cpp
int main() { escoreType type = Midterm; eStudyType studyType = Fulltime; //에러 안남! 하지만 큰 문제!! int num = Assignment3; if(type == Fulltime) { //이하 코드 생략 } return 0; }
C 스타일은 Enum은 int형이어서 비교하기 시작하면 문제가 발생
해결책 : enum class
Main.cpp
enum class eScoreType { Assignment1, Assignment2, Assignment3, Midterm, Count, }; enum class eStudyType { Fulltime, Parttime, };
Main.cpp
int main() { escoreType score = eScoreType::Midterm; eStudyType studyType = eStudyType::Fulltime; int num = eScoreType::Assignment3; //에러 if(score == Fulltime) //에러 { //이하 코드 생략 } return 0; }
예시 enum class용 정수형 명시하기
//Class.h #include<cstdint> enum class eScoreType : uint8_t { Assignment1, Assignment2, Assignment3, Midterm, Final = 0x100, // 경고! };
허나 이렇게 할 수는 없음
컴파일 안되는 예시
for(int i = eScoreType::Assignment1; i<eScoreType::Count; ++i) { // ... }
컴파일 되는 예시
for(int i = static_cast<int>(eScoreType::Assignment1); i < static_cast<int>(eScoreType::Count); ++i) { // ... }
헤더에서 초기화하기
마침내 이게 됨!
예시1
class Vector { //... private: int mX = 0; // OK int mY = 0; // OK const static int mDimension = 2; // OK static int mCount = 0; // 에러 }
예시2
class Vector{ //... private: static int mCount; }; //vector.cpp //const static이 아닌 것 때문에 계속 이렇게 해야함 int Vector::mCount = 0;
근데 이걸 써야할까?
- 선별적으로
- 커다란 코드베이스에서는 안 쓰는걸 추천
- 헤더를 변경할 때마다
- 헤더를 인클루드하는 모든 .cpp파일을 다시 빌드해야함
- 이 헤더를 다른 헤더들이 모두 인클루드 할 수도 있고
- 그 헤더들을 다시 다른 헤더들이 인클루드 하고
- 그 헤더들을 다시….
- 의존성 지옥…
- 그 헤더들을 다시….
- 그 헤더들을 다시 다른 헤더들이 인클루드 하고
- 이 헤더를 다른 헤더들이 모두 인클루드 할 수도 있고
- 헤더를 인클루드하는 모든 .cpp파일을 다시 빌드해야함
- 모듈 시스템이 도입될 때까지 기다리자 (아마 C++ 20에 들어올 듯)
정리
새로운 키워드들
- auto
- static_assert
- default/delete
- final/override
새로운 자료형들
- nullptr
- 고정 폭 정수형
- enum class
보너스
- offsetof
- 헤더 파일에서 초기화