묻공러
[C++ NOTE] Types
묻공러
묻지마공부
묻공러
전체
오늘
어제
  • 분류 전체보기 (521) N
    • C (54)
      • [코드조선] C 핵심 (35)
      • [언어본색] C 기초 (19)
    • C++ (72)
      • [루키스] C++ (9)
      • [루키스] 콜백함수 (6)
      • [루키스] STL (8)
      • [루키스] Modern C++ (11)
      • [노코프] C++ (10)
      • [노코프] Tips (16)
      • [일지] C++ (12)
    • C# (20) N
      • [루키스] C# (9)
      • [루키스] 자료구조 (3)
      • [루키스] 실전 문법 (8) N
    • 자료구조 & 알고리즘 (50)
      • [코드조선] C 자료구조 & 알고리즘 (6)
      • [합격자되기] C++ 코딩테스트 (12)
      • [루키스] C++ 자료구조 & 알고리즘 (32)
    • CS (69)
      • [널널한 개발자] CS 개론 (19)
      • [혼자 공부하는] 컴퓨터 구조 (16)
      • [혼자 공부하는] 운영체제 (18)
      • [널널한 개발자] 네트워크 (16)
    • 게임 그래픽스 (46)
      • [전북대] OpenGL (25)
      • [일지] DirectX (21)
    • 게임 엔진 - 언리얼 (123)
      • [코드조선] 언리얼 (53)
      • [코드조선] 언리얼 데디서버 (8)
      • [일지] 언리얼 (59)
      • [일지] 언리얼 (2) (3)
    • 게임 엔진 - 유니티 (14) N
      • [최적화] 유니티 (4)
      • [루키스] 유니티 (10) N
    • 게임 서버 (17)
    • 게임 수학 & 물리 (19)
      • 게임 수학 (12)
      • 게임 물리 (7)
    • GIT & GITHUB (4)
    • 영어 (18)
      • [The Outfit] 대본 공부 (11)
      • the others (7)
    • 그 외 (14)
      • In (5)
      • Out (5)
      • Review (4)

인기 글

최근 글

hELLO · Designed By 정상우.
C++/[노코프] C++

[C++ NOTE] Types

2024. 4. 1. 12:10

01. Floating Numbers 1

# 변환 방법

float은 아래와 같은 방법으로 변환된다

# converter

위에서 사용되는 이진법 부동소수점 정의에

스탠다드는 IEEE 754이고

해당 변환을 직접적으로 보여주는 사이트를 활용해서

내부를 확인할 수 있다

 


 

02. Floating Numbers 2

# 주의점

float은 근삿값으로 변환이 되기에

equality, comparison을 사용하는 것에 주의해야 한다

double은 float보다는 더욱 정밀하지만

마찬가지로 주의해야 한다

#include <iostream>
#include <cmath>
#include <iomanip>
#include <limits>

int main()
{
    const float num1 = 0.3f;
    const float num2 = 0.4f;

    if (0.7f == num1 + num2)
    {
        std::cout << "YES";
    }
    else
    {
        std::cout << "NO";
    }
}

 

# 해결 방법

아래의 almost_equal과 같은 함수를 만들어주면 된다

int ulp는 유동적으로 scale에 맞게 근사치를 조정하기 위한 부분이다

#include <iostream>
#include <cmath>
#include <iomanip>
#include <limits>

bool almost_equal(float x, float y, int ulp)
{
    const float diff = std::fabs(x - y);
    return diff <= std::numeric_limits<float>::epsilon() * std::abs(x + y) * ulp
        || diff < std::numeric_limits<float>::min();
}

int main()
{
    const float num1 = 0.3f;
    const float num2 = 0.4f;

    if (almost_equal(0.7f, num1 + num2, 1))
    {
        std::cout << "YES";
    }
    else
    {
        std::cout << "NO";
    }
}

 

# 해결 방법 - 언리얼/유니티

// 언리얼
bool FMath::IsNearlyEqual(float A, float B, float ErrorTolerance = SMALL_NUMBER)

// 유니티
bool Mathf.Approximately(float a, float b)
// 언리얼

float a = 0.1f + 0.2f;
float b = 0.3f;

if (FMath::IsNearlyEqual(a, b, 0.0001f)) 
{
    UE_LOG(LogTemp, Warning, TEXT("a and b are approximately equal"));
}
else 
{
    UE_LOG(LogTemp, Warning, TEXT("a and b are not equal"));
}
// 유니티

float a = 0.1f + 0.2f;
float b = 0.3f;

if (Mathf.Approximately(a, b)) 
{
    Debug.Log("a and b are approximately equal");
}
else 
{
    Debug.Log("a and b are not equal");
}

 

03. std::pair, tuple

pair와 tuple은 

둘 다 서로 다른 형식의 데이터를 하나로 묶어서 사용할 수 있는 

C++의 템플릿 클래스이다

 

# pair 예시

enum class ErrorCode
{
	NoError,
	Error_divide0,
	Error_other,
};

std::pair<int, ErrorCode> divide(int a, int b)
{
	if (b == 0)
	{
		return {0, ErrorCode::Error_divide0};
	}

	return {a / b, 0};
}

 

# tuple 예시

pair는 2개만 가능하지만

tuple은 2개 이상도 가능하다

#include <iostream>
#include <stdexcept>
#include <string>
#include <tuple>

std::tuple<double, char, std::string> get_student(int id)
{
    switch (id)
    {
    case 0: return { 3.8, 'A', "Lisa Simpson" };
    case 1: return { 2.9, 'C', "Milhouse Van Houten" };
    case 2: return { 1.7, 'D', "Ralph Wiggum" };
    case 3: return { 0.6, 'F', "Bart Simpson" };
    }

    throw std::invalid_argument("id");
}

int main()
{
    // 1-1번째 방법
    const auto student0 = get_student(0);
    std::cout << "ID: 0, "
        << "GPA: " << std::get<0>(student0) << ", "
        << "grade: " << std::get<1>(student0) << ", "
        << "name: " << std::get<2>(student0) << '\n';

    // 1-2번째 방법
    const auto student1 = get_student(1);
    std::cout << "ID: 1, "
        << "GPA: " << std::get<double>(student1) << ", "
        << "grade: " << std::get<char>(student1) << ", "
        << "name: " << std::get<std::string>(student1) << '\n';

    // 2번째 방법
    double gpa2;
    char grade2;
    std::string name2;
    std::tie(gpa2, grade2, name2) = get_student(2);
    std::cout << "ID: 2, "
        << "GPA: " << gpa2 << ", "
        << "grade: " << grade2 << ", "
        << "name: " << name2 << '\n';

    // 3번째 방법
    // C++17 structured binding
    const auto [gpa3, grade3, name3] = get_student(3);
    std::cout << "ID: 3, "
        << "GPA: " << gpa3 << ", "
        << "grade: " << grade3 << ", "
        << "name: " << name3 << '\n';
}

 


 

04. std::optional

pair보다 가독성이 높으니 적절히 활용하면 좋다

해당 반환 타입에 문제가 있는지 없는지를 리턴해준다

다만, 해당 타입이 valid/invalid 여부만을 리턴해주기에

더 많은 에러 정보를 리턴해주고 싶다면,

pair 혹은 tuple을 이용하면 된다

 

또한,

해당 optional이 Valid 한 지 Invalid 한 지의 여부를

구분할 수 있는 정보가 추가되기 때문에

일반적인 변수 사이즈보다 조금 더 크다

 

# 예시 1

#include <iostream>
#include <optional>

std::optional<int> divide(int a, int b)
{
	if (b == 0)
	{
		return std::nullopt;
	}
	
	return a / b;
}

int main()
{
	const auto answer = divide(10, 0);

	if (answer)
	{
		std::cout << answer.value() << std::endl;
	}
	else
	{
		std::cout << answer.value_or(0) << std::endl;
		// answeer.value_or을 통해 직접 값을 지정해 반환 가능
	}
}

 

# 예시 2 - 클래스

#include <iostream>
#include <optional>

class Cat
{
private:
	int n = 10;
public:
	void print()
	{
		std::cout << "meow" << std::endl;
	}
};

int main()
{
	std::optional<Cat> cat;// 실제로 오브젝트가 생성 X

	cat = Cat();// 오브젝트 생성 방법 1

	std::optional<Cat> cat2{ Cat() };// 오브젝트 생성 방법 2
	// 위의 2가지 방식은 임시로 Cat을 만들고 move를 하기에
	// 비효율적인 방식

	std::optional<Cat> cat3{std::in_place};// 오브젝트 생성 방법 3
	// 바로 생성하는 방식
}

 


 

05. enum class

enum class는 C++11에서 도입된 열거형(enum)의 새로운 형태이다

기존의 C++의 열거형은 값들이

해당 열거형의 범위 내에서 모두 공유되어

이름 충돌을 초래할 수 있었다

enum class는 이러한 문제를 해결하기 위해 만들어졌다 

각 열거형 멤버는 해당 열거형의 범위 내에서만 사용할 수 있으며, 

기본적으로 기본 유형과 다르게 취급된다 
간단히 말해, enum class는 열거형의 값들을 범위로 묶어주고 

각 값들에 대해 별도의 네임스페이스를 제공하여 

코드의 가독성과 안전성을 높여준다

enum class는 연산자 오버로딩 또한 가능하다

#include <cstdint>
#include <iostream>

enum color
{
    red,
    yellow,
    green = 20,
    blue
};

// enumeration types (both scoped and unscoped) can have overloaded operators
std::ostream& operator<<(std::ostream& os, color c)
{
    switch (c)
    {
        case red: os << "red";    break;
        case yellow: os << "yellow"; break;
        case green: os << "green";  break;
        case blue: os << "blue";   break;
        default: os.setstate(std::ios_base::failbit);
    }
    return os;
}

int main()
{
    color col = red;

    std::cout << "col = " << col << '\n';
}

 


 

06. Union

Union은 하나의 메모리를 공유하면서

읽는 형태를 변경하는 경우에 사용한다

memory saving이 필요한 분야에서 많이 활용된다

하지만 아래와 같은 문제점들이 발생하면서

위험한 경우가 생길 가능성이 높다

 

# 문제점 1 - undefined behavior

#include <iostream>

struct S// 4 + (4) + 8 = 16
{
	int i;// 4
	double d;// 8
};

union U// 8
{
	int i;// 4
	double d;// 8
};

int main()
{
	U u;
	u.i = 10;
	std::cout << u.i << std::endl;

	u.d = 0.3;
	std::cout << u.d << std::endl;


	//std::cout << u.i << std::endl;// undefined behavior

}

 

# 문제점 2 - Object

오브젝트가 Union에 들어오게 되면

오브젝트의 Constructor/Destructor를 반드시 작성해야 한다

작성하지 않으면, segmentation fault가 발생한다

#include <iostream>
#include <string>
#include <vector>
 
union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {}
};
 
int main()
{
    S s = {"Hello, world"};

    std::cout << "s.str = " << s.str << '\n';
    
    s.str.~basic_string();// string의 destructor
    new (&s.vec) std::vector<int>;// vector의 constructor
    
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

 

# 해결 방법

union은 위와 같은 위험성으로 인해

아래와 같은 코드에서 사용하는 방법을 사용해서

조금은 덜 위험하게 사용할 수 있다

하지만 여전히 위험한 것은 사실이고

이를 해결하기 위해 std::variant가 등장했다

#include <iostream>
 
// S has one non-static data member (tag), three enumerator members (CHAR, INT, DOUBLE), 
// and three variant members (c, i, d)
struct S
{
    enum{CHAR, INT, DOUBLE} tag;// 태그를 통해 추적
    union
    {
        char c;
        int i;
        double d;
    };
};
 
void print_s(const S& s)
{
    switch(s.tag)
    {
        case S::CHAR: std::cout << s.c << '\n'; break;
        case S::INT: std::cout << s.i << '\n'; break;
        case S::DOUBLE: std::cout << s.d << '\n'; break;
    }
}
 
int main()
{
    S s = {S::CHAR, 'a'};
    print_s(s);
    s.tag = S::INT;
    s.i = 123;
    print_s(s);
}

 


 

07. std::variant

union을 안전하게 사용하기 위해 도입되었다

type tracking information의 추가적인 공간이 필요하다

매번 호출을 할 때마다 type check를 해줘야 해서

어느 정도 overhead가 발생하지만 사용환경에 따라 적절하게 사용한다면

union보다는 std::variant를 사용하는 것이 좋다

#include <iostream>
#include <variant>

struct S
{
	int i;
	double d;
	float f;
};

union U
{
	int i;
	double d;
	float f;
};

int main()
{
	std::variant<int, double, float> v;
	std::cout << "S: " << sizeof(S) << std::endl;// 24
	std::cout << "U: " << sizeof(U) << std::endl;// 8
	std::cout << "V: " << sizeof(v) << std::endl;// 16 (8 + type tracking information)

}

 

Union의 문제점이었던 undefined behavior가

발생하지 않고

오브젝트가 Union에 들어오는 경우

오브젝트의 Constructor/Destructor를 작성해주지 않아도 된다

#include <iostream>
#include <variant>
#include <string>
#include <vector>

int main()
{
	// 문제점 1 해결
	std::variant<int, double, float> v;
	v = 10;
	
	if (auto pVal = std::get_if<double>(&v))
	{
		std::cout << *pVal << std::endl;
	}
	else
	{
		std::cout << "v is not type double" << std::endl;
	}


	// 문제점 2 해결
	std::variant<std::string, std::vector<int>> sv;
	sv = std::string("abc");
	std::cout << std::get<std::string>(sv) << std::endl;

	sv = std::vector{ 1,2,3 };

}

 

pair, tuple 혹은 optional을 사용해도 되지만

variant를 사용하면 훨씬 쉽게 코드를 작성할 수 있는 예시는 아래와 같다

enum class ErrorCode
{
	NoError,
	Error_divide0,
	Error_other,
};

std::variant<int, ErrorCode> divide(int a, int b)
{
	if (b == 0)
	{
		return ErrorCode::Error_divide0;
	}

	return a / b;
}

 


 

08. std::any

std::any는 

void* 형으로 모든 자료형의 주소를 담을 수 있는 상태에서

type info를 추가적으로 저장함으로써 조금 더 안전하게 사용할 수 있게 된다

#include <any>
#include <iostream>

int main()
{
    std::cout << std::boolalpha;

    // any type
    std::any a = 1;
    std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
    a = 3.14;
    std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
    a = true;
    std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';

    // bad cast
    try
    {
        a = 1;
        std::cout << std::any_cast<float>(a) << '\n';
    }
    catch (const std::bad_any_cast& e)
    {
        std::cout << e.what() << '\n';
    }

    // has value
    a = 2;
    if (a.has_value())
        std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';

    // reset
    a.reset();
    if (!a.has_value())
        std::cout << "no value\n";

    // pointer to contained data
    a = 3;
    int* i = std::any_cast<int>(&a);
    std::cout << *i << '\n';
}

 


 

09. Type Punning

어떤 타입을 나타내는 메모리 공간을

다른 타입으로 읽는 경우를 의미한다

 

Type Punning을 포인터 형태로 구현하면,

unsigned char*, char*, std::byte *를 제외한

포인터 캐스팅은 undefined behavior로 정의된다

 

따라서 표준적인 방법으로 만드는 방법은

memcpy를 이용하면 된다

compiler optimization은 memcpy를 최적화시켜주기에

실제 메모리 copy는 일어나지 않으면서

안전하게 Type Punning을 이용할 수 있다

 

C++ 20부터는 Type Punning을

더 편하고 안전하게 사용하기 위한 함수인

bit_cast가 도입되었다

#include <iostream>
#include <cstring>

bool isNeg(float x)
{
	unsigned int* ui = (unsigned int*)&x;
	return *ui & 0x80000000;
}

bool isNeg_memcpy(float x)
{
	unsigned int tmp;
	std::memcpy(&tmp, &x, sizeof(x));
	return tmp & 0x80000000;
}

bool isNeg_bitcast(float x)
{
	return std::_Bit_cast<uint32_t>(x) & 0x80000000;
}

int main()
{
	std::cout << isNeg(-1.0f) << std::endl;
	std::cout << isNeg_memcpy(-1.0f) << std::endl;
	std::cout << isNeg_bitcast(-1.0f) << std::endl;
}

 

주로 소켓프로그래밍에서 자주 활용된다

#include <iostream>
#include <cstring>

struct S
{
	int a;
	double d;
	float f;
};

void fn(unsigned char* address, std::size_t length)
{
	// send Object(패킷)
	// or data copy
	// or set Object
}

int main()
{
	S s;
	fn((unsigned char*)&s, sizeof(s));
}
저작자표시 비영리 변경금지 (새창열림)

'C++ > [노코프] C++' 카테고리의 다른 글

[C++ NOTE] Exceptions  (0) 2024.04.03
[C++ NOTE] Functional Programming  (1) 2024.03.31
[C++ NOTE] Template  (0) 2024.03.30
[C++ NOTE] Smart Pointer  (0) 2024.03.29
[C++ NOTE] Inheritance  (0) 2024.03.28
'C++/[노코프] C++' 카테고리의 다른 글
  • [C++ NOTE] Exceptions
  • [C++ NOTE] Functional Programming
  • [C++ NOTE] Template
  • [C++ NOTE] Smart Pointer
묻공러
묻공러
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.