이전 포스팅에서 간단히 템플릿에 대해서 설명하였습니다.

 

[C++] Template(1)

기본적으로 템플릿은 편의성과 확장성을 모두 만족할 수 있는 기법입니다. 예를 들어 매개변수로 전달받은 두 수를 더하는 함수를 만든다고 가정했을 때 매개변수는 정수형이 올 수도 실수형이

1d1cblog.tistory.com

우리가 template과 함께 typename 이라는 키워드를 사용하였습니다. 이 typename이라는 키워드는 템플릿 매개변수를 선언할 때 사용합니다. template <typename T> 처럼 사용하는 사람이 있고 template <class T> 로 사용하는 사람이 있는데 똑같이 동작합니다. typename이라는 키워드 자체를 타입이라고 명시를 시켜줄 때 사용할 수도 있습니다.

 

예를 들어 아래와 같이 코드를 작성했을 때 일반적인 상황에서는 전달받은 컨테이너가 가지고 있는 const_iterator 타입을 포인터 변수로 사용하는 코드가 됩니다. 하지만 만약에 전달받은 타입에 const_iterator라는 정적 변수가 있을 때에는 또 다른 의미를 가지게 됩니다. 그리고 x라는 전역 변수가 어딘가에 있다면 이 구문은 포인터 변수를 선언하는 코드가 아닌 단순 곱하기처럼 동작할 가능성을 지니게 됩니다.

template <typename T>
void print(const T& container)
{
	T::const_iterator * x;
}

그렇기에 이러한 문제를 방지하기 위해 이것은 타입이다 라고 명시할 수 있게 typename 키워드를 앞에 붙여서 사용할 수 있습니다.

template <typename T>
void print(const T& container)
{
	typename T::const_iterator * x;
}

템플릿으로 만들어진 클래스를 상속 받아 클래스를 하나 만들어 보겠습니다. Printer라는 클래스는 Test 변수를 하나 생성해서 printTest 함수를 호출합니다. 근데 이 Printer<T>를 상속받아 LogPrinter라는 클래스를 하나 더 만들고 이 안에서 print 함수를 호출하도록 하였습니다. 하지만 이대로 컴파일 시 에러가 나게 됩니다.

#include <iostream>

class Test {
public:
  void printTest() { std::cout << "Test"; }
};

template <typename T>
class Printer {
public:
  void print() {
    T t;
    t.printTest();
  }
};

template <typename T>
class LogPrinter : public Printer<T> {
public:  
  void lprint() {
    print();
  }
};

int main()
{
  LogPrinter<Test> lp;
  lp.lprint();
}

이 print라는 함수를 컴파일 단계에서는 기본 클래스를 명확히 할 수 없기 때문에 컴파일 할 수가 없습니다. 기본 클래스가 전달받은 타입에 따라 print라는 함수가 없을 수도 있기 때문입니다. 이러한 에러를 해결하기 위해서는 아래 방법을 사용할 수 있습니다.

// this 사용
template <typename T>
class LogPrinter : public Printer<T> {
public:  
  void lprint() {
    this->print();
  }
};

// using을 통해 기본 클래스에 함수가 있음을 알려줌
template <typename T>
class LogPrinter : public Printer<T> {
public:
  using Printer<T>::print;
  void lprint() {
    print();
  }
};

// 명시적으로 기본 클래스의 함수 호출
template <typename T>
class LogPrinter : public Printer<T> {
public:  
  void lprint() {
    Printer<T>::print();
  }
};

이렇게 템플릿을 사용하면 코드의 수를 줄일 수 있을 뿐 아니라 유지보수 및 기타 부분에 대해서 장점을 지니게 됩니다. 하지만 이런 템플릿도 잘못된 방법으로 사용할 경우 쓸데없이 코드가 비대해질 수 있습니다. 아래 코드로 예를 들어보겠습니다.

 

아래와 같이 타입과 크기를 전달받는 클래스가 있을 때 아래와 같이 사용한다면 Array<int, 10>::invert와 Array<int, 20>::invert에 대한 인스턴스화 되게 됩니다. 

template <typename T, int size>
class Array {
public:
    void invert();
};

int main()
{
    Array<int, 10> AInt10;
    Array<int, 20> AInt20;
    
    AInt10.invert();
    AInt20.invert();
}

이러한 중복을 막기 위해서는 굳이 사이즈 값은 템플릿으로 받지 않고 멤버 함수의 매개변수로 받아 처리할 수도 있습니다.

template <typename T>
class Array {
public:
    void invert(int nSize);
};

int main()
{
    Array<int> AInt10;
    Array<int> AInt20;
    
    AInt10.invert(10);
    AInt20.invert(20);
}
반응형

'Programming > C++' 카테고리의 다른 글

[C++] Template(1)  (2) 2023.09.19
[C++] std::forward_list, std::list  (0) 2023.07.18
[C++] std::array, std::vector  (0) 2023.07.12
[C++] 람다 함수  (0) 2023.04.07
[Effective C++] 4. 설계 및 선언  (0) 2022.11.14

기본적으로 템플릿은 편의성과 확장성을 모두 만족할 수 있는 기법입니다. 예를 들어 매개변수로 전달받은 두 수를 더하는 함수를 만든다고 가정했을 때 매개변수는 정수형이 올 수도 실수형이 올 수도 있습니다. 이럴 때 함수 오버로딩을 통해 각 매개변수 타입에 대한 함수를 다중 정의할 수 있습니다.

int Add(int a, int b);
double Add(double a, double b);

만약 간단하지 않은 하나의 함수를 여러가지로 다중 정의했을 때 문제가 발생하는 경우 모든 함수를 고치거나 손봐야 하는 경우가 생깁니다. 이럴 경우 사용할 수 있는 것이 템플릿입니다. 템플릿은 아래와 같이 template이라는 키워드를 사용합니다. 이렇게 정의한 코드를 사용자가 호출하면 컴파일러에 의해 다중 정의 코드가 자동으로 만들어지게 됩니다.

template <typename T>
T Add(T a, T b);

 사용자는 호출할 때 자료형을 명시해도 되고 안해도 됩니다.

#include <iostream>
using namespace std;

template <typename T>
T Add(T a, T b)
{
  return a+b;
}

int main()
{
  cout << Add(3,4) << endl;
  cout << Add(3.3, 4.4) << endl;
  cout << Add<int>(3,4) << endl;
  cout << Add<int>(3.3, 4.4) << endl;
}

만약 위와 같이 자료형을 명시한다면 매개변수에 다른 타입을 넣어도 그 자료형에 맞게 캐스팅됩니다.

이 템플릿을 함수뿐 아니라 템플릿에도 적용이 가능합니다. 함수에서 사용하던 것과 다른 점이 있다면 무조건 타입을 명시해 주어야 합니다.

#include <iostream>
using namespace std;

template <typename T>
class valueSave {
public:
  valueSave(T value) : m_value(value) {}
  T getValue() { return m_value; }
private:
  T m_value;
};

int main()
{
  valueSave<int> vInt(10);
  cout << vInt.getValue() << endl;

  valueSave<double> vDouble(11.11);
  cout << vDouble.getValue() << endl;

  valueSave<int> vCast(13.13);
  cout << vCast.getValue() << endl;
}

템플릿을 사용하는 클래스의 선언과 정의를 분리하기 위해서는 아래와 같이 사용합니다.

template <typename T>
class valueSave {
public:
  valueSave(T value) : m_value(value) {}
  T getValue();
private:
  T m_value;
};

template <typename T>
T valueSave<T>::getValue()
{
  return m_value;
}

템플릿을 다중으로 사용할 수 있으며 그리고 템플릿으로 받는 값을 하나의 매개변수로도 사용할 수 있습니다.

#include <iostream>
using namespace std;

template <typename T, int nSize>
class valueSave {
public:
  valueSave(T value) : m_value(value) {}
  T getValue();
  void printSize() { cout << nSize << endl; }
private:
  T m_value;
};

template <typename T, int nSize>
T valueSave<T, nSize>::getValue()
{
  return m_value;
}

int main()
{
  valueSave<int, 3> vInt(10);
  cout << vInt.getValue() << endl;
  vInt.printSize();
}

이 템플릿의 타입과 값을 디폴트로 설정할 수 있습니다.

#include <iostream>
using namespace std;

template <typename T = int, int nSize = 5>
class valueSave {
public:
  valueSave(T value) : m_value(value) {}
  T getValue();
  void printSize() { cout << nSize << endl; }
private:
  T m_value;
};

template <typename T, int nSize>
T valueSave<T, nSize>::getValue()
{
  return m_value;
}

int main()
{
  valueSave<> vInt(10);
  cout << vInt.getValue() << endl;
  vInt.printSize();
}

템플릿을 사용하면 자료형에 상관없이 함수를 재사용할 수 있다는 장점이 있지만 만약 특정 자료형은 다르게 처리해야 하는 경우도 있습니다. 함수에서 이를 이용하기 위해서는 반환형, 매개변수 타입이 모두 같아야 합니다.

#include <iostream>
using namespace std;

template <typename T>
T Add(T a, T b) { return a+b; }

template <>
char Add(char a, char b) 
{
  cout << "char template" << endl;
  return 'c';
}

int main()
{
  cout << Add<int>(3,4) << endl;
  cout << Add<double>(3.3, 4.4) << endl;
  cout << Add<char>('a','b') << endl;
}

 물론 클래스 템플릿에서도 사용할 수 있습니다. 하지만 클래스를 선언할 때 특수화할 클래스에 타입을 명시해 주어야 합니다.

#include <iostream>
using namespace std;

template <typename T>
class valueSave {
public:
  valueSave(T value) : m_value(value) {}
  T getValue() { return m_value; }
private:
  T m_value;
};

template <>
class valueSave<char> {
public:
  valueSave(char value) : m_value(value) {}
  char getValue() {
    cout << "Char Class" << endl;
    return m_value; 
  }
private:
  char m_value;
};

int main()
{
  valueSave<int> vInt(10);
  cout << vInt.getValue() << endl;

  valueSave<char> vChar('A');
  cout << vChar.getValue() << endl;
}

템플릿 특수화된 클래스에서 선언부와 정의부를 분리할 때는 따로 template <>을 기술하지 않아도 됩니다.

#include <iostream>
using namespace std;

template <typename T>
class valueSave {
public:
  valueSave(T value) : m_value(value) {}
  T getValue() { return m_value; }
private:
  T m_value;
};

template <>
class valueSave<char> {
public:
  valueSave(char value) : m_value(value) {}
  char getValue();
private:
  char m_value;
};

char valueSave<char>::getValue()
{
  cout << "Char Class" << endl;
  return m_value; 
}

int main()
{
  valueSave<int> vInt(10);
  cout << vInt.getValue() << endl;

  valueSave<char> vChar('A');
  cout << vChar.getValue() << endl;
}

 

반응형

'Programming > C++' 카테고리의 다른 글

[C++] Template(2)  (0) 2023.09.22
[C++] std::forward_list, std::list  (0) 2023.07.18
[C++] std::array, std::vector  (0) 2023.07.12
[C++] 람다 함수  (0) 2023.04.07
[Effective C++] 4. 설계 및 선언  (0) 2022.11.14

이전 포스팅에서 살펴본 std::array, std::vector의 경우 담겨져 있는 임의 데이터를 확인하기엔 좋지만 데이터의 추가/삭제가 잦은 경우에는 비효율적이라고 소개한적 있습니다.

 

[C++] std::array, std::vector

우리가 보통 배열을 사용한다면 아래와 같은 코드로 사용합니다. int arr[10]; int* pArr = new int[10]; 위와 같이 사용할 경우 정적 배열의 경우 고정된 크기로 설정이 가능하고 인덱스를 넘어 참조하려

1d1cblog.tistory.com

데이터의 추가 삭제가 많은 경우에는 연결리스트를 사용하는게 더욱 효율적일 수 있지만 사용자가 임의로 연결리스트를 구현하기 위해서 노드를 구현하고 그를 위해 동적할당을 한다면 memory leak이 발생할 가능성이 큽니다. 이럴 때 사용할 수 있게 std::array와 마찬가지로 std::forward_list를 제공합니다.

 

std::forward_list는 기본적인 연결리스트 기능을 갖추고 있고 추가 기능을 제공합니다. std::forward_list에서 데이터를 삽입하기 위해서는 push_front()와 insert_after()를 사용합니다. push_front 함수는 연결리스트 가장 앞에 데이터를 삽입하고 insert_after 함수는 반복자를 이용해서 원하는 위치 뒤로 데이터를 삽입합니다.

#include <iostream>
#include <forward_list>
using namespace std;

void printFwdlist(std::forward_list<int>& list) {
	for ( int i : list ) {
		cout << i << ' ';
	} cout << endl;
}

int main(int argc, char* argv[]) 
{
	std::forward_list<int> fwd_list = {1,2,3};
	
	fwd_list.push_front(0);
	printFwdlist(fwd_list);
	
	auto it = fwd_list.begin();
	
	fwd_list.insert_after(it, 5);
	fwd_list.insert_after(++it, 7);
	
	printFwdlist(fwd_list);
}

삭제의 경우에는 pop_front()와 erase_after()를 사용합니다. 삽입과 마찬가지로 각각 첫번째, 그리고 해당 위치 다음 데이터를 조작합니다. erase_after의 경우에는 두가지 형태가 제공되는데 그냥 반복자만 넘겨 해당 위치 다음의 데이터를 제거하는 것과 위치값을 2개를 넘겨 해당 범위의 데이터를 모두 삭제시키는 형태가 있습니다.

#include <iostream>
#include <forward_list>
using namespace std;

void printFwdlist(std::forward_list<int>& list) {
	for ( int i : list ) {
		cout << i << ' ';
	} cout << endl;
}

int main(int argc, char* argv[]) 
{
	std::forward_list<int> fwd_list = {1,2,3,4,5,6,7,8};
	
	fwd_list.pop_front();
	printFwdlist(fwd_list);
	
	auto it = fwd_list.begin();
	
	fwd_list.erase_after(it);
	printFwdlist(fwd_list);
	
	fwd_list.erase_after(it, fwd_list.end());
	printFwdlist(fwd_list);
}

위 함수 외에도 remove, remove_if 함수가 제공되는데 remove 함수는 매개변수 값이 std::forward_list 내에 존재할 경우 지워주게 되고 remove_if 함수는 람다함수를 매개변수로 넘겨 사용하며 함수 몸체에 등호 연산을 통해 해당 데이터를 지우게 됩니다.

#include <iostream>
#include <forward_list>
using namespace std;

void printFwdlist(std::forward_list<int>& list) {
	for ( int i : list ) {
		cout << i << ' ';
	} cout << endl;
}

int main(int argc, char* argv[]) 
{
	std::forward_list<int> fwd_list = {1,2,3,4,5,6,7,8};
	
	fwd_list.remove(1);
	printFwdlist(fwd_list);
	
	fwd_list.remove_if([](int n) { return n > 4; });
	printFwdlist(fwd_list);
}

이 외에도 정렬을 위한 sort 함수, 역순으로 뒤집는 reverse 함수, 인접하는 중복 데이터를 제거하는 unique 함수 등이 있습니다.

#include <iostream>
#include <forward_list>
using namespace std;

void printFwdlist(std::forward_list<int>& list) {
	for ( int i : list ) {
		cout << i << ' ';
	} cout << endl;
}

int main(int argc, char* argv[]) 
{
	std::forward_list<int> fwd_list = {4,1,3,2,5,9,7,8};
	printFwdlist(fwd_list);
	
	fwd_list.sort();						// 오름차순 정렬
	printFwdlist(fwd_list);
	
	fwd_list.sort(std::greater<int>());		// 내림차순 정렬
	printFwdlist(fwd_list);
	
	fwd_list.reverse();
	printFwdlist(fwd_list);
	
	std::forward_list<int> fwd_list2 = {1,1,2,3,3,4,5,4};
	fwd_list2.unique();
	printFwdlist(fwd_list2);
}

 

std::forward_list - cppreference.com

template<     class T,     class Allocator = std::allocator > class forward_list; (1) (since C++11) (2) (since C++17) std::forward_list is a container that supports fast insertion and removal of elements from anywhere in the container. Fast random acce

en.cppreference.com

std::foward_list에 존재하지 않는 기능이 추가된 연결 리스트가 std::list 입니다. std::list의 경우 이중연결리스트로 구현되어 있습니다. std::list에서는 임의의 위치에 데이터를 삽입/삭제할 때 _after가 빠진 함수 insert(), erase()를 사용하며 std::forward_list에서는 제공하지 않는 push_back(), pop_back()도 제공합니다.

#include <iostream>
#include <list>
using namespace std;

void printList(std::list<int>& list) {
	for ( int i : list ) {
		cout << i << ' ';
	} cout << endl;
}

int main(int argc, char* argv[]) 
{
	std::list<int> list = {1,2,3};
	list.push_back(4);
	
	printList(list);
	
	list.insert(list.begin(), 0);
	printList(list);
	
	list.insert(list.end(), 5);
	printList(list);
	
	list.push_back(6);
	list.pop_front();
	list.pop_back();
	
	printList(list);
}

 

std::list - cppreference.com

template<     class T,     class Allocator = std::allocator > class list; (1) (2) (since C++17) std::list is a container that supports constant time insertion and removal of elements from anywhere in the container. Fast random access is not supported.

en.cppreference.com

 

반응형

'Programming > C++' 카테고리의 다른 글

[C++] Template(2)  (0) 2023.09.22
[C++] Template(1)  (2) 2023.09.19
[C++] std::array, std::vector  (0) 2023.07.12
[C++] 람다 함수  (0) 2023.04.07
[Effective C++] 4. 설계 및 선언  (0) 2022.11.14

우리가 보통 배열을 사용한다면 아래와 같은 코드로 사용합니다.

int arr[10];
int* pArr = new int[10];

위와 같이 사용할 경우 정적 배열의 경우 고정된 크기로 설정이 가능하고 인덱스를 넘어 참조하려 할 경우 Segmanatation fault가 발생합니다. 동적 배열의 경우 new에 대한 delete가 빠질 경우 그에 따른 메모리 누수(memory leak)가 발생할 수 있습니다.

 

이럴 때 C++ 스타일의 배열인 std::array를 사용하면 좋습니다. std::array를 사용할 경우 메모리도 자동으로 할당, 해제하여 사용하기에도 안전합니다. 

 

array의 경우 아래처럼 template을 통해 타입을 지정할 수 있고 두 번째 인자로 크기를 상수로 넘겨주게 됩니다.

std::array를 사용하려면 #include<array>를 추가해주어야 합니다. 기본적으로 접근은 특정 인덱스에 접근하는 방법과 정해진 위치(앞, 뒤)에 접근하는 방법이 있는데 특정 인덱스에 접근하기 위해서는 기존처럼 [] 연산자를 사용하거나 at 함수를 사용할 수 있습니다. 혹은 정해진 위치를 접근하기 위해 front() 함수나 back() 함수를 통해 접근도 가능합니다.

#include <iostream>
#include <array>
using namespace std;

int main(int argc, char* argv[]) 
{
	std::array<int, 10> arr;
	arr[0] = 1;
	cout << "arr[0] = " << arr[0] << endl;
	cout << "arr.at(0) = " << arr.at(0) << endl;
	cout << "arr.front() = " << arr.front() << endl;
}

만약에 인덱스 범위를 넘어서 참조하려 한다면 기존 배열의 경우에는 segmentation fault가 났겠지만 std::array는 좀 다릅니다. [] 연산자를 통해 접근할 경우 쓰레기 값이 출력되고 at을 통해 접근 시 std::out_of_range 에러가 납니다.

이렇게 되면 예외 처리를 통해 에러를 방지할 수 있습니다.

#include <iostream>
#include <array>
using namespace std;

int main(int argc, char* argv[]) 
{
	std::array<int, 3> arr{1,2,3};	// 초기화

	try {
		for ( int i =0; i <= 3; i++) {
			cout << "arr.at(" << i << ") = " << arr.at(i) << endl;
		}
	} catch ( const std::out_of_range& e) {
		cout << "out of range" << endl;
	}
}

이 std::array를 매개변수로 던지는 방법은 const 참조형으로 넘기는 방법과 data 함수를 통해서 저장되어 있는 배열을 넘기는 방법이 있습니다.

#include <iostream>
#include <array>
using namespace std;

void print2constarg(const std::array<int,3>& arr) {
	for ( int i : arr ) {
		cout << i;
	}
	cout << endl;
}

void print2data(const int* pArr, std::size_t size) {
    for (std::size_t i = 0; i < size; i++) {
		cout << pArr[i] << ' ';
	}
	cout << endl;
}

int main(int argc, char* argv[]) 
{
	std::array<int, 3> arr{1,2,3};

	print2constarg(arr);
	print2data(arr.data(), arr.size());
}

위에서 print2constarg의 매개변수 std::array의 크기를 고정시켰는데 만약에 크기가 다른 array를 받으면 에러가 발생합니다.

이것을 방지하기 위해서 변수 크기를 템플릿화 해서 사용한다면 다른 크기의 array도 사용이 가능해집니다. 이렇게 타입도 템플릿 매개변수로 지정할 수 있지만 타입이 기본 자료형이 아닌 경우에 기본 출력이 아닌 방법을 사용해야 할 수 있기 때문에 타입은 지정하는 게 안전하다고 생각합니다.

template <size_t T>
void print2constarg(const std::array<int,T>& arr) {
	for ( int i : arr ) {
		cout << i;
	}
	cout << endl;
}

void print2data(const int* pArr, std::size_t size) {
    for (std::size_t i = 0; i < size; i++) {
		cout << pArr[i] << ' ';
	}
	cout << endl;
}

int main(int argc, char* argv[]) 
{
	std::array<int, 11> arr{1,2,3};

	print2constarg(arr);
	print2data(arr.data(), arr.size());
}

마지막으로 반복자를 통해 요소를 접근하는 방법입니다. begin은 첫 번째 원소를 가리키는 반복자를 반환, end는 마지막 원소의 다음을 가리키는 반복자를 반환합니다.

#include <iostream>
#include <array>
using namespace std;

template <size_t T>
void print2constarg(const std::array<int,T>& arr) {
	for ( auto i : arr ) {
		cout << i;
	}
	cout << endl;
	
	for ( auto it = arr.begin(); it!= arr.end(); ++it ) {
		cout << *it;
	}
	cout << endl;
}


int main(int argc, char* argv[]) 
{
	std::array<int, 3> arr{1,2,3};

	print2constarg(arr);
}

여기에 const_iterator를 사용할 수 있는 cbegin, cend 그리고 reverse_iterator를 사용하는 rbegin, rend가 있지만 반복자에 const가 붙는 것, begin/end가 반대인 것이 생각하면 됩니다.

 

std::array - cppreference.com

template<     class T,     std::size_t N > struct array; (since C++11) std::array is a container that encapsulates fixed size arrays. This container is an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-s

en.cppreference.com

C 스타일의 배열보다는 std::array가 더 낫지만 array도 몇가지 단점이 있는데 크기를 상수로 전달해야 하고 요소를 추가/삭제할 수 없다는 제약이 있습니다. 이러한 제약을 해결한 std::vector가 있습니다.

 

vector는 생성 시에 굳이 크기를 넘겨주지 않아도 됩니다. 혹은 넘겨주고 그 요소들을 특정 값으로 모두 초기화할 수도 있습니다.

#include <iostream>
#include <vector>
using namespace std;

int main(int argc, char* argv[]) 
{
	std::vector<int> vecInt;
	std::vector<int> vecInt2 = {1,2,3};
	std::vector<int> vecInt3(10);
	std::vector<int> vecInt4(10,0);	
}

vector는 크기가 가변이기 때문에 추가/삭제도 가능한데 추가를 위해 insert(), push_back()를 사용합니다. insert의 경우 추가를 원하는 위치를 같이 넘겨 원하는 위치에 값을 추가하고 push_back의 경우에는 맨 뒤에 요소를 추가합니다.

#include <iostream>
#include <vector>
using namespace std;

int main(int argc, char* argv[]) 
{
	std::vector<int> vecInt;
	
	vecInt.push_back(0);
	vecInt.push_back(30);
	
	for ( int i : vecInt ) {
		cout << i << ' ';
	} cout << endl;
	vecInt.insert(vecInt.begin()+1,10);
	
	for ( int i : vecInt ) {
		cout << i << ' ';
	} cout << endl;
}

추가 시에 벡터가 가질 수 있는 capacity를 체크 후 모자르다면 capacity를 늘리고 추가합니다. capacity는 vector가 수용할 수 있는 요소 개수 정도라고 생각하면 되고 크기(size)는 현재 vector가 가지고 있는 요소 개수라고 생각하면 됩니다. 

 

push_back의 경우에는 단순히 뒤에 추가하는 작업이기 때문에 O(1)의 시간 복잡도를 나타내지만, insert의 경우 특정 위치에 요소를 추가하면 요소들을 복사/이동 해야하기 때문에 O(n)의 시간 복잡도를 나타냅니다. 그렇기 때문에 추가/삭제가 빈번히 일어나는 경우 vector 자료구조를 사용하는게 오히려 독이될 수 있습니다.

 

추가에 있어 emplace 함수가 있는데 이 함수는 객체를 요소로 추가할 때 임시 객체를 생성할 필요 없이 사용 가능하게 해주는 함수입니다. 이렇게 봤을 땐 무조건 emplace가 좋을거 같지만 형 변환 등에 취약할 수 있습니다. 관련해서 정리가 잘 되어 있는 블로그 포스팅 링크 남겨둡니다.

 

emplace_back 과 push_back 의 차이

item 타입의 생성자가 타입을 인자로 받는다면? push_back 함수는 '객체' 를 집어 넣는 형식으로, 객체가 없이 삽입을 하려면 "임시객체 (rvalue) " 가 있어야 합니다. 또는 암시적 형변환이 가능하다면,

openmynotepad.tistory.com

 

std::vector - cppreference.com

template<     class T,     class Allocator = std::allocator > class vector; (1) (2) (since C++17) 1) std::vector is a sequence container that encapsulates dynamic size arrays. The elements are stored contiguously, which means that elements can be acces

en.cppreference.com

 

반응형

'Programming > C++' 카테고리의 다른 글

[C++] Template(1)  (2) 2023.09.19
[C++] std::forward_list, std::list  (0) 2023.07.18
[C++] 람다 함수  (0) 2023.04.07
[Effective C++] 4. 설계 및 선언  (0) 2022.11.14
[Effective C++] 3. 자원 관리  (0) 2022.11.07

Qt Console Application을 만들고 Run을 시켰는데 Console 창이 안뜨고 아래 Application Output Pannel에 결과가 나오는 경우가 있습니다.

QT -= gui

CONFIG += c++17 console
CONFIG -= app_bundle

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
        main.cpp

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
#include <QCoreApplication>
#include <iostream>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    std::cout << "hi\n";
    qDebug() << "qDebug() << hi\n";

    return a.exec();
}

이럴 경우 좌측 배너에서 [Projects] > [Build & Run] > [Run]을 클릭 후 메인 화면에서 Run 부분에 Run in terminal을 체크 후 실행하면 console 창이 뜨는 것을 볼 수 있습니다.

체크 안한 경우 Output에
체크 한 경우 Console(Terminal) 창에

 

반응형

보안이나 특정 상황에 의해서 외부망에 연결하지 않는 내부 서버들이 있습니다. 이럴 경우 시간 동기화에 문제가 발생할 수 있는데 윈도우 NTP 서버 / 클라이언트 기능을 이용하여 위 문제를 해결할 수 있습니다. 하지만 이 해결 방법은 하나의 인터넷에 연결된 서버가 같은 망에 적어도 1개는 존재해야 가능합니다.

 

구성은 아래와 같습니다. 외부망과 연결된 서버는 time.windows.com 같은 Time Server와 시간을 동기화 해주고 그 동기화 된 시간을 내부서버가 Windows NTP 기능을 이용해 다시 동기화 합니다.

 

먼저 외부와 연결된 서버에 설정을 해줍니다.

 

[Windows 방화벽] > [고급 설정] > [인바인드 규칙] > [새 규칙] 으로 들어가 규칙을 추가합니다.

NTP의 경우 123번 포트 사용합니다.

[Win] + [R] 으로 실행을 실행해서 gpedit.msc를 입력해 로컬 그룹 정책 편집기를 실행합니다.

[컴퓨터 구성] > [관리 템플릿] > [시스템] > [Windows 시간 서비스] > [시간 공급자] > [Windows NTP 서버 사용]을 사용으로 바꿔줍니다.

[Windows 시간 서비스] > [글로벌 구성 설정] 에서 AnnounceFlags를 5로 설정합니다.

실행에서 services.msc을 입력해 서비스를 실행합니다.

Windows Time을 클릭 후 시작 유형을 자동으로 바꾼 후 시작을 눌러줍니다.

다음으로 내부 서버 설정입니다.

 

실행에서 regedit.exe를 입력해 레지스트리 편집기를 실행 후 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient\SpecialPollInterval 값을 원하시는 동기화 주기로 입력합니다. (초 단위)

실행에서 services.msc를 입력해 서비스를 실행 후 Windows Time의 시작 유형을 자동(지연된 시작)으로 설정 후 시작합니다.

마지막으로 Windows 시간에서 [날짜 및 시간 설정 변경] > [날짜 및 시간] > [인터넷 시간] > [설정 변경]을 클릭합니다.

이제 설정한 서버의 IP를 입력 후 지금 업데이트를 통해 테스트 후 확인을 눌러 설정을 마무리 합니다.

출처 : https://minimax95.tistory.com/entry/NTP-%EC%84%9C%EB%B2%84%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%8B%9C%EA%B0%84-%EB%8F%99%EA%B8%B0%ED%99%94

반응형

기존에 설치된 Qt를 재설치 했더니 Qt5 버전에서 Quick Applicaiton Create가 안되는 증상이 있었습니다. 확인해보니 Qt Creator 10.0을 설치하면 Build System에 qmake가 없는 증상이 있고 그렇기 때문에 project를 생성할 수 없었습니다.

해당 문제를 해결하기 위해 Qt Creator 9.0으로 다운그레이드 하여 사용하겠습니다. 아래 링크를 통해 Qt Creator를 재설치 해줍니다.

 

Index of /official_releases/qtcreator/9.0/9.0.0

 

download.qt.io

qt-creator-opensource-windows-x86_64-9.0.0.exe을 설치합니다.

 

경로는 기본으로 설치합니다.

설치가 완료됐으면 방금 설치한 QtCreator 9.0을 설치합니다. 이제 다시 Project를 만들어 볼려고 해도 Kit에 아무것도 없거나 선택아 안될 것입니다.

그렇기 때문에 기존에 설치된 qmake를 등록하고 그에 맞는 Kit 설정도 해야 합니다. [Edit] > [Preference] > [Qt Version]에 Add를 눌러 기존 설치된 mingw8.1.0의 qmake를 설정합니다.

Apply 후 [Kits]로 넘어가서 Add 후 아래처럼 마무리 합니다.

그 후 Create Project를 하면 Supported Platforms에 Desktop이 추가가 됐고

Project가 정상적으로 만들어진 것을 확인할 수 있습니다.

위 포스팅은 아래 유튜브 영상을 참고하여 제작하였습니다.

 

반응형

Qt Creator를 통해 빌드한 결과물인 exe 파일을 단독으로 실행하면 아래처럼 오류 메시지가 나오게 됩니다.

실행파일을 실행하기 위해선 Qtcore에 명시한 dll들이 필요한데 그 dll들이 없어서 위와 같이 오류가 뜨게 됩니다. 이러한 dll들을 자동으로 추가해주는 windeployqt라는 툴도 있습니다.

 

[Qt] windeployqt 사용하기

Qt5부터 사용할 수 있는 windeployqt는 빌드 시 생성한 실행파일을 단독으로 실행할 수 있게 필요한 라이브러리를 자동으로 추가해주는 툴입니다. 그 후 프로젝트가 저장된 폴더로 이동하면 Release

1d1cblog.tistory.com

이번에는 dll 없이 단독으로 실행할 수 있게 Static build 환경을 만들어보겠습니다.

 

먼저 Qt Maintenance를 실행해 빌드 환경과 Source를 설치합니다.

설치가 완료되었으면 Qt가 설치된 폴더로 이동 후 Static 이라는 이름의 폴더를 하나 만들어줍니다.

그리고 그 Static 폴더 내부에 5.15.2(버전) 폴더를 하나 더 생성 후 Qt\5.15.2\Src 폴더를 Qt\Static에 복사합니다.

복사가 다 됐다면 Qt\Static\Src\qtbase\mkspecs\win32-g++ 폴더의 qmake.conf 에 아래 내용을 추가합니다.

QMAKE_LFLAGS += -static -static-libgcc
QMAKE_CFLAGS_RELEASE -= -O2
QMAKE_CFLAGS_RELEASE += -Os -momit-leaf-frame-pointer
DEFINES += QT_STATIC_BUILD

다음으로 CMD를 실행 후 환경 변수를 추가합니다. mingw은 설치된 버전에 맞춥니다.

다음으로 Qt Console을 실행하고 Qt\Static\Src로 이동 후 아래 코드를 실행합니다.

주의할 점은 -prefix 뒤 폴더는 생성한 폴더에 맞춰야 합니다.

configure -static -release -platform win32-g++ -prefix C:\Qt\Static\5.15.2 -qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -qt-freetype -opengl desktop -no-openssl -opensource -confirm-license -make libs -nomake tools -nomake examples -nomake tests

configure이 끝났으면 마지막으로 아래 두 명령을 실행합니다. 아래 두 작업에는 대략 30분 ~ 1시간이 소요됩니다.

mingw32-make -k -j4
mingw32-make -k install

만약 Make 시에 아래처럼 Qml 쪽 에러가 나오고 JSC::Yarr::newlineCreate라는 에러 메시지가 나온다면 https://www.python.org/downloads/ 링크에서 Python을 다운로드 해줍니다.

설치 시에 Add Python.exe to PATH를 체크합니다.

make가 모두 끝났으면 Qt\Static\5.15.2\mkspecs\win32-g++에 있는 qmake.conf를 열어 아래 코드를 추가합니다.

CONFIG += static

이제 Qt Creator를 실행해 [Edit] > [Preference]를 실행합니다. [Qt Versions]에서 Add를 누른 후 Qt\Static\5.15.2\bin에 qmake.exe를 선택한 후 Apply 해줍니다.

다음으로 [Kits]로 이동해 Add 후 아래처럼 Qt Version에 위에서 만든 버전을 선택 후 적용합니다.

마지막으로 Projects에서 위에서 만든 Kit를 적용 후 Build합니다.

 

makersweb - Windows환경에서 mingw로 Qt 5.10 정적(static)빌드

환경: 윈도우7, Qt5.10 기본적으로 qt.io에서 배포된 Qt는 사전 빌드된 라이브러리들이 동적으로 링크된다. 즉, 응용프로그램이 실행 될때 사전 빌드된 DLL을 동적으로 참조한다. 문제는 이런 환경에

makersweb.net

 

반응형

+ Recent posts