1. 네임스페이스: 코드의 구조화와 이름 충돌 방지
네임스페이스는 C++에서 코드를 조직화하고 이름 충돌을 방지하는 데 필수적인 도구입니다. 특히 대규모 프로젝트에서 여러 라이브러리나 모듈을 사용할 때 유용합니다.
1.1 네임스페이스의 필요성
- 이름 충돌 방지: 동일한 이름의 함수나 변수가 다른 모듈에 존재할 때, 네임스페이스를 사용하여 이를 구분할 수 있습니다.
- 코드 조직화: 관련된 기능을 그룹화하여 코드의 가독성과 유지보수성을 높입니다.
1.2 기본 문법
네임스페이스는 namespace
키워드로 정의됩니다. 아래는 간단한 예제입니다.
#include <iostream>
namespace MyNamespace {
void displayMessage() {
std::cout << "Hello from MyNamespace!" << std::endl;
}
}
int main() {
MyNamespace::displayMessage(); // 네임스페이스를 명시하여 함수 호출
return 0;
}
1.3 여러 개의 네임스페이스 사용
여러 네임스페이스를 사용하여 코드를 더욱 체계적으로 정리할 수 있습니다.
#include <iostream>
namespace MathFunctions {
int add(int a, int b) {
return a + b;
}
}
namespace StringFunctions {
void printGreeting() {
std::cout << "Welcome to the world of C++!" << std::endl;
}
}
int main() {
int sum = MathFunctions::add(5, 10);
std::cout << "Sum: " << sum << std::endl;
StringFunctions::printGreeting();
return 0;
}
1.4 using
지시어와 선언
using
을 사용하면 네임스페이스를 생략하고 코드를 간결하게 작성할 수 있습니다.
using namespace MathFunctions;
int main() {
int result = add(3, 7); // MathFunctions:: 생략
std::cout << "Result: " << result << std::endl;
return 0;
}
1.5 중첩 네임스페이스
네임스페이스는 중첩하여 사용할 수도 있습니다. 이는 더 복잡한 프로젝트에서 코드를 더 세밀하게 조직화할 때 유용합니다.
namespace Outer {
namespace Inner {
void display() {
std::cout << "Inside Inner namespace" << std::endl;
}
}
}
int main() {
Outer::Inner::display(); // 중첩된 네임스페이스 사용
return 0;
}
1.6 익명 네임스페이스
익명 네임스페이스는 파일 내에서만 사용할 수 있는 네임스페이스로, 전역 변수의 사용을 제한하고 이름 충돌을 방지합니다.
#include <iostream>
namespace {
void display() {
std::cout << "This is an anonymous namespace" << std::endl;
}
}
int main() {
display(); // 익명 네임스페이스 내의 함수 호출
return 0;
}
2. 템플릿: 코드 재사용과 일반화
템플릿은 C++에서 타입에 구애받지 않는 일반화된 프로그래밍을 가능하게 합니다. 이를 통해 코드의 재사용성과 유연성을 높일 수 있습니다.
2.1 함수 템플릿
함수 템플릿은 다양한 데이터 타입에 대해 동일한 로직을 적용할 수 있습니다.
#include <iostream>
using namespace std;
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
cout << "최대값(3, 7): " << max(3, 7) << endl; // int
cout << "최대값(2.5, 1.5): " << max(2.5, 1.5) << endl; // double
cout << "최대값('A', 'B'): " << max('A', 'B') << endl; // char
return 0;
}
2.2 클래스 템플릿
클래스 템플릿은 다양한 타입을 처리할 수 있는 클래스를 정의할 때 사용됩니다.
#include <iostream>
using namespace std;
template <typename T>
class Stack {
private:
T* arr;
int top;
int capacity;
public:
Stack(int size) {
arr = new T[size];
capacity = size;
top = -1;
}
void push(T x) {
if (top == capacity - 1) {
cout << "스택 오버플로우" << endl;
return;
}
arr[++top] = x;
}
T pop() {
if (top == -1) {
cout << "스택 언더플로우" << endl;
return T();
}
return arr[top--];
}
};
int main() {
Stack<int> stackInt(10);
stackInt.push(1);
stackInt.push(2);
cout << stackInt.pop() << endl; // 출력: 2
Stack<double> stackDouble(10);
stackDouble.push(3.14);
cout << stackDouble.pop() << endl; // 출력: 3.14
return 0;
}
2.3 템플릿 특수화
특정 타입에 대해 별도의 동작을 정의할 수 있습니다.
#include <iostream>
using namespace std;
template<typename T>
void print(T value) {
cout << "일반 값: " << value << endl;
}
template<>
void print<char*>(char* value) {
cout << "문자열 값: " << value << endl;
}
int main() {
print(100); // 일반 값 출력
print("Hello"); // 문자열 값 출력 (특수화)
return 0;
}
2.4 템플릿 매개변수
템플릿은 타입뿐만 아니라 값도 매개변수로 받을 수 있습니다.
#include <iostream>
using namespace std;
template <typename T, int size>
class Array {
private:
T arr[size];
public:
void fill(T value) {
for (int i = 0; i < size; ++i) {
arr[i] = value;
}
}
void print() {
for (int i = 0; i < size; ++i) {
cout << arr[i] << " ";
}
cout << endl;
}
};
int main() {
Array<int, 5> intArray;
intArray.fill(10);
intArray.print(); // 출력: 10 10 10 10 10
Array<double, 3> doubleArray;
doubleArray.fill(3.14);
doubleArray.print(); // 출력: 3.14 3.14 3.14
return 0;
}
2.5 템플릿과 상속
템플릿 클래스는 상속을 통해 확장할 수 있습니다. 이를 통해 더 복잡한 구조를 만들 수 있습니다.
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
void display() {
cout << "Base class" << endl;
}
};
template <typename T>
class Derived : public Base<T> {
public:
void show() {
cout << "Derived class" << endl;
}
};
int main() {
Derived<int> d;
d.display(); // Base class
d.show(); // Derived class
return 0;
}
3. 멀티스레딩: 동시성과 병렬성
멀티스레딩은 프로그램이 동시에 여러 작업을 수행할 수 있도록 하는 기술입니다. C++11부터 표준 라이브러리에서 스레드를 지원합니다.
3.1 기본 개념
- 스레드: 프로세스 내에서 실행되는 경량 작업 단위.
- 동기화: 여러 스레드가 공유 자원에 접근할 때 데이터 일관성을 유지하기 위한 기법.
3.2 멀티스레딩 예제
아래는 두 개의 스레드를 생성하여 동시에 작업을 수행하는 예제입니다.
#include <iostream>
#include <thread>
void printNumbers(int id) {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread " << id << ": " << i << std::endl;
}
}
int main() {
std::thread t1(printNumbers, 1);
std::thread t2(printNumbers, 2);
t1.join();
t2.join();
std::cout << "All threads finished." << std::endl;
return 0;
}
3.3 동기화: 뮤텍스 사용
여러 스레드가 공유 자원에 접근할 때 뮤텍스를 사용하여 동기화를 수행합니다.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void safePrint(int id) {
std::lock_guard<std::mutex> lock(mtx); // 자동으로 뮤텍스 잠금 및 해제
for (int i = 0; i < 5; ++i) {
std::cout << "Thread " << id << ": " << i << std::endl;
}
}
int main() {
std::thread t1(safePrint, 1);
std::thread t2(safePrint, 2);
t1.join();
t2.join();
std::cout << "All threads finished." << std::endl;
return 0;
}
3.4 스레드 풀(Thread Pool)
스레드 풀은 미리 생성된 스레드들을 관리하여 작업을 효율적으로 분배하는 방법입니다. C++에서는 직접 구현하거나 라이브러리를 사용할 수 있습니다.
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
class ThreadPool {
public:
ThreadPool(size_t numThreads) {
for (size_t i = 0; i < numThreads; ++i) {
threads.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this] { return !tasks.empty() || stop; });
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
stop = true;
}
condition.notify_all();
for (std::thread &thread : threads) {
thread.join();
}
}
template <class F>
void enqueue(F &&f) {
{
std::unique_lock<std::mutex> lock(queueMutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop = false;
};
int main() {
ThreadPool pool(4);
for (int i = 0; i < 8; ++i) {
pool.enqueue([i] {
std::cout << "Task " << i << " is running on thread " << std::this_thread::get_id() << std::endl;
});
}
return 0;
}
3.5 스레드 간 통신: 조건 변수
조건 변수는 스레드 간의 동기화를 위해 사용되며, 특정 조건이 충족될 때까지 스레드를 대기시킬 수 있습니다.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void printId(int id) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
std::cout << "Thread " << id << " is running" << std::endl;
}
void go() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all();
}
int main() {
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(printId, i);
}
std::cout << "5 threads ready to race..." << std::endl;
go();
for (auto &th : threads) {
th.join();
}
return 0;
}
결론
C++의 네임스페이스, 템플릿, 멀티스레딩은 각각 코드의 구조화, 재사용성, 동시성을 높이는 데 중요한 역할을 합니다. 이러한 고급 기능을 적절히 활용하면 더 효율적이고 유지보수하기 쉬운 프로그램을 작성할 수 있습니다. C++의 강력한 기능을 마스터하여 더 나은 소프트웨어를 개발해 보세요!
'프로그래밍 > C++' 카테고리의 다른 글
최신 C++ 기능으로 더 효율적이고 안전한 코드 작성하기 (1) | 2025.02.01 |
---|---|
C++ 프로젝트 실습: 간단한 계산기 만들기부터 디버깅 및 테스트까지 (0) | 2025.02.01 |
C++ 예외 처리: 안정적인 프로그램을 위한 필수 기법 (0) | 2025.02.01 |
C++ STL 컨테이너: 벡터, 리스트, 맵, 셋 활용 가이드 (0) | 2025.02.01 |
C++ 파일 입출력: 파일 읽기와 쓰기 (0) | 2025.02.01 |