반응형

std::string을 사용하면서 printf처럼 formatting 기능을 사용하고 싶었다.

혹시 새로운 기능이 없을까 하고 검색해보니 c++20에 std::format이라는 기능이 추가되었다고 한다.

std::format은 아래와 같이 std::string_view를 인자로 받는다.

template<class... Args>
string format(string_view fmt, const Args&... args);

string_view이기 때문에 그냥 const char*를 넘겨도 된다.

 

기본 사용방법은 아래와 같다.

std::format("{} {}!", "Hello", "world");	// Hello world!

{} 위치에 배치된다고 보면 된다.

 

{} 이곳에 들어갈 여러가지 기능들은 너무 많아서 링크로 대신한다.

필요할때 마다 검색해서 사용해봐야 될 것 같다.

https://en.cppreference.com/w/cpp/utility/format/formatter#Standard_format_specification

 

std::formatter - cppreference.com

template struct formatter; (since C++20) The enabled specializations of formatter define formatting rules for a given type. Enabled specializations meet the Formatter requirements. In particular, they define callables parse and format. For all types T and

en.cppreference.com

 

format을 사용하기 위해서는 Visual Studio 2019를 16.10 버전 이상으로 올려야 하고

C++ 버전은 latest로 변경해야 한다.

 

Vector3를 String으로 변경하는 간단한 코드를 예제로 남기고 마무리하면 될 것 같다.

class Vector3
{
public:
  float x_, y_, z_;
  
public:
  std::string toString()
  {
    return std::format("( {:.2f}, {:.2f}, {:.2f} )", x_, y_, z_);
  }
};
반응형
Posted by msparkms
,
반응형

C++은 메모리를 직접 관리할 수 있는 언어이다.

직접 관리는 자유를 주는 대신 책임도 따른다.

메모리를 직접 관리하면 실수에 의해 누수가 일어날 수 있다.

이를 막기 위해 가비지 컬렉션등 메모리 관리를 하는 알고리즘을 구현하거나 라이브러리등을 가져와야한다.

 

C++의 스마트 포인터를 이용하면 포인터를 사용하는 것 처럼 사용할 수 있으면서 (연산자 오버로딩)

메모리 관리는 스마트 포인터 객체에게 맡길 수 있다.

 

스마트 포인터 종류를 간단히 정리해보자.

보통 메모리 누수가 발생하는 이유는 해당 메모리를 바라보는 포인터들이 여러개일 경우

맨 마지막에 해당 메모리에 접근한 포인터가 메모리를 해제해야하는데 이를 관리하기가 쉽지 않다.

 

그래서 사용하는 전략이 대표적으로 두가지가 있는데

해당 메모리를 바라보는 포인터는 무조건 하나만 존재하게 만드는 고유 소유권 방식과

해당 메모리를 바라보는 포인터가 생기면 카운트를 올려주고 해제하면 카운트를 내려서

카운트가 모두 제거되었을때 실제 메모리를 제거해주는 레퍼런스 카운팅 방식이 있다.

 

다른 이유는 delete를 하기전에 예외등이 발생하여 delete가 실행이 안되는 경우이다.

이때는 스마트 포인터 객체로 감싸져 있기 때문에

예외가 발생했을때 해당 객체가 제거되고 메모리 해제부분이 실행되어 해결이 된다.

 

unique_ptr은 고유 소유권 방식의 스마트 포인터이다.

C++14부터 잘 지원되는 auto와 std::make_unique()를 이용하면 쉽게 unique_ptr을 생성할 수 있다.

make_unique()를 이용하면 생성자의 인자를 바로 넘겨줄 수 있어 간단하고

한번 감싸져 있는 형태여서 예외가 발생했을때 누수가 발생할 염려도 없다.

 

기본적인 사용법은 아래와 같다.

기본적으로 포인터와 같은 연산자를 이용할 수 있다.

get()을 통해 내부의 포인터를 직접 얻어올 수 있다.

reset()을 통해 내부 포인터를 해제하거나 내부 포인터를 다른 포인터로 교체할 수 있다.

release()는 내부 포인터를 돌려주며 소유권을 끊어주어서 직접 해제해야 한다.

class Test
{
public:
	Test(int x) : x_(x) {};

	void Print()
	{
		std::cout << x_ << std::endl;
	}

private:
	int x_;
};

void PrintTest(std::unique_ptr<Test> testPtr)
{
	testPtr->Print();
}

int main()
{
	auto myTest = std::make_unique<Test>(1);
	myTest->Print();

	PrintTest(std::make_unique<Test>(2));

	myTest.get()->Print();		// internal pointer
	myTest.reset();			// to nullptr
	if (!myTest)
	{
		std::cout << "myTest is empty" << std::endl;
	}
	myTest.reset(new Test(3));	// to new pointer
	myTest->Print();

	auto myInternalTest = myTest.release();
	if (!myTest)
	{
		std::cout << "myTest is empty" << std::endl;
	}
	myInternalTest->Print();
	delete myInternalTest;		// manually delete

	return 0;
}
반응형
Posted by msparkms
,
반응형

C++11에 유니폼 초기화가 추가되었다.

{}를 이용한 초기화여서 Brace-Initialization 이라고도 부르는것 같다.

 

C++11에 {}를 이용해서 초기화 문법을 따로 추가한 이유는 무엇일까??

사실 이전부터 일부 상황에서는 {}를 이용한 초기화를 사용하고 있었고

일부 상황에서는 ()를 이용한 초기화를 사용하고 있었다.

 

구조체와 배열의 경우에는 {}를 이용해서 초기화를 하고 있었고

하지만 클래스는 ()를 이용해서 생성자를 호출해서 초기화 하는식으로 처리가 되고 있었다.

struct A
{
  int x, y;
};

class B
{
  public:
    B(int x, int y) : mX(x), mY(y) {}
  private:
    int mX, mY;
};


A a = {10, 20};			// {}
B b(10, 20);			// ()

int c[4] = {1, 2, 3, 4};	// {}

C++11에서는 이 부분을 모두 {}를 이용하는 유니폼 초기화로 문법을 통일시켰다고 보면 될 것 같다.

=을 붙이거나 붙이지 않거나 모두 유니폼 초기화를 사용할 수 있다.

A a1 = {10, 20};
B b1 = {10, 20};

A a2{10, 20};
B b2{10, 20};

유니폼 초기화는 일반 자료형에도 사용 가능하다. 아래 예제에서 {}로 초기화할 경우 0이 들어간다.

int a = 1;
int b(1);

// uniform-initialization
int c = {1};	
int d{1};
int e{};	// 0

기왕 유니폼 초기화 기능을 넣고 문법을 통일하면서 한가지 기능을 더 추가하였는데

바로 축소 변환 방지(narrowing) 기능이다.

이는 암묵적 캐스팅이 진행될 때 기존 데이터의 손실이 발생할 경우 에러를 처리해주는 기능을 말한다.

예를 들자면 int에 double 값을 넣을 경우에 소수부분은 손실되게 되는데 이러한 상황을 막는 것이다.

void PrintInt(int x)
{
  std::cout << "Num : " << x << std::endl;
}

A i { 3.14, 3.14 };	// warning
B j { 3.14, 3.14 };	// error
int k { 3.14 };		// error

PrintInt(3.14);		// warning
PrintInt({ 3.14 });	// error

내가 테스트한 환경에서는 구조체의 경우에는 warning 처리로 끝나기는 했고 나머지의 경우에는 모두 축소 변환 에러가 발생했다. (VS 2019 - C++17)

 

클래스 멤버 변수를 선언하면서 바로 초기화 하거나 초기화 리스트에서 배열을 초기화 할때도 사용할 수 있다.


다음은 직접 리스트 초기화와 복제 리스트 초기화에 대한 부분을 간단히 정리하고 넘어가려고 한다.

위에 예제를 보면 a = {}의 형식이거나 아니면 a {} 형식으로 초기화를 하였다.

 

a = {}와 같은 형태를 복제 리스트 초기화라고 하고

a {}와 같은 형태를 직접 리스트 초기화라고 한다.

 

그냥 생성자 호출과 복사 생성자 호출 (= 연산자) 의 느낌이라고 대충 생각해도 될 것 같다.

 

여기서 이야기할 내용은 {}를 auto로 받았을 경우에 타입이 무엇이냐에 대한 것이다.

{}를 auto로 받으면 initializer_list<>으로 변경되고 처리가 되는데 C++17 이전과 이후에 약간 변화가 있어서 정리하려고 한다.

 

C++17 이전은 모두 initializer_list<>로 처리가 되고 C++17은 복제 리스트 초기화만 initializer_list<>로 처리된다.

// before C++ 17
auto a1 = { 1 };	// initializer_list<int>
auto a2 = { 1, 2 };	// initializer_list<int>
auto a3{ 1 };		// initializer_list<int>
auto a4{ 1, 2 };	// initializer_list<int>
auto a5 = { 1, 1.1 };	// error

// after C++ 17
auto a1 = { 1 };	// initializer_list<int>
auto a2 = { 1, 2 };	// initializer_list<int>
auto a3{ 1 };		// int
auto a4{ 1, 2 };	// error
auto a5 = { 1, 1.1 };	// error

C++17 이후 a4의 경우에는 초기화 리스트가 아닌 타입을 추론하게 되는데

이때 하나의 값에 의한 추론만 진행하기 때문에 값이 여러개라고 에러가 발생하게 된다.

그리고 a5의 경우에는 복제 초기화 리스트라도 다른 타입의 값을 넘긴다면 추론에 실패한다.

 

참고로 내가 테스트한 환경에서는 C++14로 낮춰도 a4 부분이 에러가 발생하기는 했다. (VS2019 - C++14)

 

마지막으로 어떤 타입인지 직접 출력해보고 싶으신 분들은

typeinfo의 typeid를 이용해서 콘솔로 출력해보면 좋을 것 같다.

반응형
Posted by msparkms
,
반응형

C++ 17에 std::string_view가 추가되었다.

기존에 std::string이 있는데 std::string_view가 따로 추가된 이유는 무엇일까?

 

문자열을 다루는 함수를 만든다고 해보면 매개변수를

const char* 혹은 const std::string& 으로 만드는 것이 보통이다.

이 두가지 선택은 각각 장단점을 가지게 된다.

 

const char*를 선택했다면 문자열 리터럴을 포함한 문자열 포인터를 받는데 용이하다.

대신 std::string에서 제공해주는 많은 기능을 사용할 수 없다.

 

const std::string&을 선택했다면 std::string의 기능을 사용할 수 있지만

문자열 리터럴을 받는다면 std::string 객체가 생성된다.

 

함수를 오버로딩해서 const char*, const std::string& 함수를 모두 만들면 해결은 되지만

뭔가 좋아보이지는 않는다.

 

두가지 장점을 모두 가져가기 위해 추가된 것이 std::string_view이다.

std::string_view는 내부에 포인터와 길이정보를 가진다.

그래서 문자열 포인터를 바로 받을 수 있으면서 std::string의 기능을 제공해준다.

포인터와 길이 정보만 가지고 있기 때문에 보통 레퍼런스가 아닌 값을 전달하는 방식으로 구현한다.

using namespace std::string_view_literals;

void StringViewTest(std::string_view strv)
{
    std::cout << "str : " << strv.data() << " length : " << strv.length() << std::endl;
}

StringViewTest("abcdefg");

std::string TestString("Test");
StringViewTest(TestString);

auto sv = "My Test String View"sv;
StringViewTest(sv);

간단한 예제는 위와 같다.

std::string_view를 매개변수로 받는 함수로 문자열 리터럴, std::string, std::string_view를 처리할 수 있다.

 

참고로 using namespace std::string_view_literals;를 추가하면

문자열 뒤에 sv를 붙여줌으로써 string_view 리터럴을 사용할 수 있다.

반응형
Posted by msparkms
,