std::fstream은 "파일"에 대한 입력 및 출력을 지원하는 클래스로 파일 스트림 클래스라고도 불립니다.

(std::ifstream와 std::ofstream의 기능을 모두 포함하고 있어 파일을 읽고 쓸 수 있습니다)

 

std::fstream은 <fstream> 헤더 파일에서 정의되며, std::fstream 클래스 내부에 구현된 함수를 사용하여 파일을 열고 닫습니다.

 

아래는 std::fstream 클래스의 주요 멤버 함수입니다.

open() : 파일을 엽니다.
close() : 파일을 닫습니다.
read() : 파일에서 데이터를 읽습니다.
write() : 파일에 데이터를 씁니다.
seekg() : 파일 내에서 읽기 위치를 이동합니다.
seekp() : 파일 내에서 쓰기 위치를 이동합니다.
tellg() : 읽기 위치를 반환합니다.
tellp() : 쓰기 위치를 반환합니다.
eof() : 파일 끝에 도달했는지 확인합니다.

 

먼저, 파일을 읽고 쓰는 간단한 코드를 한번 보도록 하겠습니다.

예제: 파일 읽고 쓰기

#include <iostream>
#include <fstream>

int main() {
    std::fstream fs;
    fs.open("example.txt", std::ios::in | std::ios::out | std::ios::binary);
    if (!fs) {
        std::cout << "파일 열기 실패" << std::endl;
        return 1;
    }
    // 파일 읽기
    char buffer[256];
    fs.read(buffer, 256);
    std::cout << buffer << std::endl;

    // 파일 쓰기
    fs.write("Hello World!", 12);
    fs.close();

    return 0;
}
먼저 fs라는 이름의 fstream 인스턴스를 활용하여 fstream 내부함수인 open 함수를 호출합니다,
파일이름은 example.txt 이고, 파일 처리 방식을 입출력이 모두 가능한 바이너리 형식으로 지정했습니다.
(여러 개의 열기 모드를 지정할 때는 비트 OR 연산자(|)를 사용)
아래는 open 함수의 주요 열기 모드입니다.
  ios_base::in : 파일을 읽기 위해 엽니다.
  ios_base::out : 파일을 쓰기 위해 엽니다.
  ios_base::app : 파일 끝에 추가하기 위해 엽니다.
  ios_base::binary : 이진 파일로 엽니다.
파일 읽기에 실패한 경우 읽기 실패에 대한 로깅을 하고 프로그램을 종료하고,
그렇지 않은 경우라면 read 함수로 파일 내용을 읽은 다음 buffer 라는 이름의 배열에 저정한 후 콘솔 출력합니다.
그리고 Hello World! 를 파일 끝에 추가로 쓰고 파일을 닫습니다.

 

예제 : 로그 파일 생성

로그 파일을 생성할 때는 이전 로그 내용을 보존하면서 새로운 로그 내용을 추가해야 하므로 std::fstream::out | std::fstream::app 모드로 파일을 열어, 새로운 로그 내용이 파일 끝에 추가되게끔 함으로써 이전 로그 내용을 계속 유지할 수 있습니다.

#include <iostream>
#include <fstream>

int main() {
    std::fstream log_file;
    log_file.open("log.txt", std::fstream::out | std::fstream::app);
    if (!log_file.is_open()) {
        std::cerr << "Failed to open log file." << std::endl;
        return -1;
    }
    log_file << "New log message." << std::endl;
    log_file.close();
    return 0;
}

 

위 예제에서는 log.txt 파일을 쓰기 모드로 열면서 파일 끝에 추가 쓰기 모드를 지정하여 로그 파일에 새로운 로그 메시지를 추가합니다. 만약 파일을 열지 못했을 경우, is_open() 함수를 사용하여 파일이 열렸는지 확인합니다.

 

fstream과 버퍼

 

std::fstream은 기능적 특성상 내부적으로 파일 입출력 작업을 위한 버퍼를 가지고 있습니다.

버퍼는 일시적으로 데이터를 저장하고, 저장된 데이터를 파일에 쓰거나 파일에서 읽어들일 때 사용됩니다.

 

std::fstream 클래스는 내부적으로 두 개의 버퍼를 가지고 있습니다.

하나는 입력 버퍼(input buffer)이고, 다른 하나는 출력 버퍼(output buffer)입니다.

입력 버퍼는 파일에서 읽어들인 데이터를 저장하는데 사용되며, 출력 버퍼는 파일에 쓰기 위한 데이터를 저장하는데 사용됩니다

 

std::fstream 클래스에서는 버퍼링을 사용하여 입출력 작업의 성능을 향상시킬 수 있습니다.

버퍼링을 사용하면 데이터를 파일에 쓰거나 파일에서 읽어들이는 작업을 수행할 때, 매번 파일에 직접 접근하지 않고 버퍼에 데이터를 저장하거나 버퍼에서 데이터를 읽어들입니다. 이렇게 함으로써 파일 입출력 작업이 더욱 효율적으로 수행될 수 있습니다.

 

std::fstream 클래스에서는 기본적으로 출력 버퍼링이 사용되며, 입력 버퍼링은 사용되지 않습니다.

즉, 출력 작업은 버퍼에 쌓인 데이터가 일정 크기 이상이 되거나, 파일을 닫을 때까지 버퍼에 저장되어 있게 됩니다.

반면에 입력 작업은 즉시 처리되며, 파일에서 데이터를 읽어들이면 입력 버퍼에 저장하지 않고 즉시 읽어들입니다.

 

버퍼링을 제어하기 위해서는 std::fstream 클래스의 멤버 함수인 flush()와 rdbuf()를 사용할 수 있습니다.

flush() 함수는 출력 버퍼에 저장된 데이터를 파일에 쓰는 작업을 수행하며, rdbuf() 함수는 입력 및 출력 버퍼에 대한 스트림 버퍼 포인터를 반환합니다. 이를 통해 버퍼를 직접 제어할 수 있습니다.

 

fstream 내부 버퍼와 운영체제의 버퍼

std::fstream 내부 버퍼와 운영체제(OS)의 버퍼는 서로 독립적으로 작동합니다.

std::fstream 내부 버퍼는 C++ 라이브러리에서 제공되는 버퍼링 기능으로, 입출력 연산의 성능을 향상시키기 위한 것인 반면,

운영체제의 버퍼는 실제 파일 시스템에 접근하여 입출력 연산을 수행하는 데 사용됩니다.

 

std::fstream 클래스의 객체를 생성하면, 내부적으로 파일과 연결된 운영체제의 파일 디스크립터를 생성하고, 파일 시스템에서 파일을 열

어야 합니다. 이 때, 운영체제는 해당 파일에 대한 버퍼를 생성하여 파일 시스템에서 데이터를 읽어들이거나 쓰기 작업을 수행합니다

 

std::fstream 클래스에서는 입출력 작업을 수행할 때, 먼저 C++ 내부 버퍼에 데이터를 저장하고, 일정량 이상의 데이터가 쌓이면 운영체제의 버퍼에 데이터를 쓰거나, 운영체제의 버퍼에서 데이터를 읽어들입니다. 이 때, C++ 내부 버퍼와 운영체제의 버퍼는 서로 독립적으로 작동하기 때문에, C++ 내부 버퍼에 데이터를 쓴 후에도, 바로 운영체제의 버퍼에 데이터가 쓰이는 것이 아니라, 내부 버퍼에 데이터가 쌓이게 됩니다. 이후에, 일정량 이상의 데이터가 쌓이면, C++ 라이브러리에서 버퍼의 내용을 운영체제의 버퍼로 옮겨주고, 운영체제의 입출력 함수를 호출하여 실제 파일 시스템에서 데이터를 읽거나 쓰게 됩니다

 

즉, std::fstream 내부 버퍼와 운영체제의 버퍼는 서로 다른 기능을 수행하면서도, 데이터를 주고받는 과정에서 서로 협력하여 작동합니다. 이러한 구조는 입출력 연산의 성능을 최적화하기 위한 것입니다.

 

fstream을 통해 파일로부터 데이터를 읽어들일 때 운영체제의 역할

 

std::fstream 클래스를 사용하여 파일로부터 데이터를 읽어들일 때, 운영체제(OS)는 다음과 같은 역할을 수행합니다

1. 파일 시스템에서 파일을 열기: std::fstream 객체가 생성되면, 운영체제는 파일 시스템에서 해당 파일을 열어야 합니다.
    파일이 존재하지 않거나, 권한이 없는 경우 등에는 파일 열기에 실패할 수 있습니다
2. 파일 읽기: 파일이 열리면, std::fstream 클래스의 멤버 함수를 이용하여 데이터를 읽어들일 수 있습니다.
    데이터를 읽어들일 때, 운영체제는 해당 파일의 내용을 버퍼에 읽어들입니다.
    버퍼는 운영체제의 내부 버퍼와 std::fstream 클래스의 내부 버퍼 두 가지가 있습니다.
3. 버퍼의 데이터를 프로그램으로 복사: 운영체제가 파일의 데이터를 버퍼에 읽어들이면, C++ 라이브러리는 이 데이터를 std::fstream      클래스의 내부 버퍼로 복사합니다. std::fstream 클래스는 내부적으로 버퍼를 사용하여 파일의 데이터를 저장합니다.
   이렇게 복사된 데이터는 프로그램에서 처리될 수 있는 형태가 되며, 읽어들인 데이터는 프로그램에서 이용됩니다
4. 파일 닫기: 데이터를 모두 읽어들인 후에는, 파일을 닫아야 합니다. 파일을 닫으면, 해당 파일에 대한 운영체제의 자원이 반환됩니다

위와 같은 과정을 통해, 운영체제는 파일 시스템에서 파일을 읽어들이고, std::fstream 클래스는 내부적으로 데이터를 버퍼에 저장합니다. 이러한 구조는 파일 입출력 작업을 효율적으로 수행하기 위한 것입니다.

 

 

seekg

 

std::fstream 클래스는 파일 입출력 작업을 수행하기 위한 다양한 멤버 함수를 제공합니다. seekg 함수는 그 중 하나로, 파일 내에서 위치를 이동하는데 사용됩니다.

seekg 함수는 파일 포인터의 위치를 이동시킵니다. 파일 포인터는 파일에서 데이터를 읽어들이거나 쓰기 위해 사용되는 위치 정보를 담고 있는 변수입니다. seekg 함수를 이용하면, 파일 포인터를 파일 내의 원하는 위치로 이동시킬 수 있습니다.

seekg 함수는 다음과 같은 구조를 가집니다

 

 

 

std::fstream& seekg (streampos pos);
std::fstream& seekg (streamoff off, ios_base::seekdir way);
  • 첫 번째 형식의 seekg 함수는, 파일 포인터를 지정된 위치로 이동시킵니다. pos 매개변수는 파일 내에서 이동시킬 위치를 나타내며, streampos 타입으로 지정됩니다.
  • 두 번째 형식의 seekg 함수는, 상대적인 위치와 이동 방향을 이용하여 파일 포인터를 이동시킵니다. off 매개변수는 현재 위치에서 이동할 바이트 수를 나타내며, way 매개변수는 이동 방향을 나타내는 ios_base::seekdir 열거형 값 중 하나를 지정합니다.

예를 들어, seekg(10)은 파일 포인터를 파일의 10번째 위치로 이동시킵니다. seekg(-5, std::ios_base::cur)은 현재 파일 포인터 위치에서 5바이트 앞으로 이동시킵니다.

seekg 함수는 std::ios::cur (현재 위치), std::ios::beg (파일 시작 위치), std::ios::end (파일 끝 위치)와 같은 ios::seekdir 값으로 이동 방향을 지정할 수 있습니다. 이러한 방법을 이용하면 파일 포인터를 특정 위치가 아닌, 파일의 시작이나 끝 부분으로 이동시킬 수 있습니다.

 

아래는 seekg 함수의 사용 예제입니다. 예제에서는 파일에서 10바이트를 읽어들이기 위해, 파일 포인터를 파일의 10번째 위치로 이동시킨 후 데이터를 읽어들이는 방법을 보여줍니다

 

#include <fstream>
#include <iostream>

int main() {
    std::ifstream infile("data.txt", std::ios::in | std::ios::binary);

    if (!infile) {
        std::cerr << "Failed to open file." << std::endl;
        return 1;
    }

    // 파일 포인터를 10번째 위치로 이동시킴
    infile.seekg(10, std::ios::beg);

    char data[11] = {0};
    infile.read(data, 10);

    std::cout << "Data read from file: " << data << std::endl;

    infile.close();

    return 0;
}
위 예제에서, seekg 함수를 이용하여 파일 포인터를 10번째 위치로 이동시킨 후 read 함수를 이용하여 10바이트를 읽어들입니다. 이렇게 파일 포인터를 원하는 위치로 이동시켜 파일 내의 원하는 위치에서 데이터를 읽어들일 수 있습니다.

 

 

seekg(0, std::ios::end)는 파일 포인터를 파일의 끝 부분으로 이동시키는 역할을 합니다.

두 번째 인자인 std::ios::end는 이동 방향을 나타내며, 파일의 끝에서부터 이동하라는 의미입니다. 첫 번째 인자인 0은 파일 끝으로부터 0바이트 떨어진 위치로 이동하라는 의미이므로, 실제로는 파일의 끝 부분으로 파일 포인터가 이동됩니다.

이렇게 파일 포인터를 파일 끝 부분으로 이동시키면, 파일의 크기를 계산하거나 파일에 새로운 데이터를 추가할 때 유용합니다. 예를 들어, 파일의 크기를 계산하기 위해서는 파일 포인터를 끝 부분으로 이동시켜 파일의 위치를 알아내고, 그 값을 파일의 크기로 사용할 수 있습니다. 또한, 파일 끝에 새로운 데이터를 추가하기 위해서도 파일 포인터를 끝 부분으로 이동시켜야 합니다.

 

src.seekg(beg, std::ios::beg)는 파일 포인터를 파일의 시작 부분으로부터 beg 바이트 떨어진 위치로 이동시키는 역할을 합니다.

첫 번째 인자인 beg는 파일 포인터를 이동할 바이트 수를 나타내며, 두 번째 인자인 std::ios::beg는 이동 방향을 나타냅니다. std::ios::beg는 파일의 시작부터 이동하라는 의미입니다.

예를 들어, src.seekg(10, std::ios::beg)은 파일의 시작 부분으로부터 10바이트 떨어진 위치로 파일 포인터를 이동시킵니다. 이렇게 파일 포인터를 원하는 위치로 이동시켜 파일 내의 원하는 위치에서 데이터를 읽어들일 수 있습니다.

 

std::fstream src(frag, std::fstream::in); 
src.seekg(0, std::ios::end); 
src.seekg(beg, std::ios::beg);
위 코드는 파일을 열어서 파일 포인터를 파일의 끝으로 이동시킨 후, 다시 파일의 시작 부분으로부터 beg바이트 떨어진 위치로 파일 포인터를 이동시키는 역할을 합니다.
첫 번째 줄에서 std::fstream::in 모드로 파일을 열어서 src 객체를 생성합니다. 이때, 파일을 읽기 모드로 열기 때문에 파일 내용을 수정할 수 없고, 읽기만 가능합니다.
두 번째 줄에서 src.seekg(0, std::ios::end)를 호출하여 파일 포인터를 파일의 끝으로 이동시킵니다. 이렇게 파일 포인터를 파일의 끝으로 이동시키면, 파일의 크기를 계산하거나 파일에 새로운 데이터를 추가할 때 유용합니다.
세 번째 줄에서 src.seekg(beg, std::ios::beg)를 호출하여 파일 포인터를 파일의 시작 부분으로부터 beg바이트 떨어진 위치로 이동시킵니다. 이렇게 파일 포인터를 원하는 위치로 이동시켜서 파일 내의 원하는 위치에서 데이터를 읽어들일 수 있습니다.
즉, 이 코드는 파일에서 데이터를 읽어들이기 위해서 파일 포인터를 파일의 시작 부분으로부터 beg바이트 떨어진 위치로 이동시킨 후, 파일의 끝 부분에서부터 파일 포인터를 이동시켜 파일의 크기를 계산하는 방법을 사용합니다

기본적으로는 가능성을 표현할 때는 문장에 "probably (not) " 를 적극적으로 활용할 것

 

A: 내일 비가 올 것 같아.
B: 그럴 가능성이 높겠지.
   하지만 기상 예보에 따르면 오후에는 구름이 조금 걷히고 해가 떠올라서 그 전에 빨래를 널어놓는 것이 좋을 거야.
A: 그래, 그러면 내일 아침에 빨래를 하고 가야겠다.

A: It's probably going to rain tomorrow.
B: Yeah, it's quite likely. But according to the weather forecast,
    there might be some clearing in the afternoon and the sun could come out,
     so it's a good idea to hang your laundry out before then.
A: Okay, then I'll do my laundry in the morning tomorrow.
  • "빨래를 널다"는 "hang the laundry" 또는 "hang up the laundry"
    "빨래를 걷다"는 "take down the laundry"

 

A: 이번 분기 실적은 어떻게 될까요?
B: 잘될 가능성이 높아 보입니다. 우리 회사는 새로운 제품 출시를 통해 시장에서 큰 인기를 얻었고,
     경쟁사보다 좋은 가격으로 제공하고 있으니까요.
A: 그렇다면 재고 관리도 신경써야겠네요.
B: 맞아요. 재고가 떨어졌을 때 대비해서 미리 생산량을 늘리는 것이 좋겠어요.

A: How do you think our performance will be this quarter?
B: It's probably going to be good.
    Our company has gained significant popularity in the market through the launch of new products
    and offering them at better prices than our competitors.
A: Then, we should also pay attention to inventory management.
B: Right. It's a good idea to increase our production volume in advance to prepare for when our inventory runs low.

 

  • 비교 표현
    When comparing A to B, you'll find that B has this additioanl functionality
    In contrast to A, B offers this particular feature
    Compared to A, B boasts this particular function

~할 가능성이 높다: There's a good chance that + ~

이번 주말에 비가올 가능성이 높다
There is a good chance that it'll rain.(it's going to rain today)
 
조심하지 않으면 길을 잃을 가능성이 높아
There is a good chance we're goning to get lost if we ate not careful
 
걔한테 말하진 않았는데 이미 알고 있을 가능성이 높아
I haven't told him about this.There is a good chance that he already knows.
 
*good chance: '가능성'; 원어민들이 '좋은 기회'라고 이해하는 일은 없음 (반대의미로 가능성이 낮다라는 표현에 사용하진 않음)
 
A: 제가 그 프로젝트에 대한 계획을 아무도에게 말하지 않았는데, 그것을 이미 다른 사람들이 알고 있는 것 같아요.
B: 그렇다면 그것이 담당자인 Tom's bread and butter 중 하나인 것 같네요.
    그는 회사 내에서 가장 알아봤을만한 사람 중 하나에요.
A: 그렇게 생각해 보니 맞아요.
    비밀로 유지하려고 해왔고 그걸 말해서 불편한 분위기를 만들고 싶진 않지만, 어쩔 수 없이 그것을 언급해야 할 것 같아요.
B: 물론이죠. "Beat around the bush"하지 마시고 직설적으로 이야기해야죠.
   그렇게 했을 때, 톰이 이미 알고 있다면, 그것을 인정하게 될 것입니다

A: There is a good chance that other people already know about my plans for the project,
     even though I haven't told anyone ( it seems like other people already know about it)
B: Then, that might be one of Tom's bread and butter.
      He's one of the most knowledgeable people in the company.
A: Come to think of it, you're right.
    I've been trying to keep it a secret and not create a secretive atmosphere by mentioning it,
    but I think I'll have to bring it up eventually.
B: Of course. Don't beat around the bush and be straightforward.
     If Tom already knows, he'll acknowledge it when you mention it that way.
 
 
  • Tom's bread and butter : Tom 의 핵심 전문 분야 (Bread and butter"는 기본적이면서 중요한 것을 의미)
  • Beat around the bush : 어떤 주제나 문제를 직접적으로 다루지 않고, 우회적으로 말하거나 중얼거리는 것
  • Admit"은 "인정하다"라는 의미로, 주로 부정적인 것을 인정할 때 사용. 한편, "Acknowledge"는 "인정하다" 또는 "인사하다"라는 의미로, 양호한 것과 부정적인 것 모두를 포괄적으로 다룰 수 있음 (즉, "If Tom already knows, he'll admit it when you mention it that way."라고 하면, Tom이 이미 알고 있다는 것이 부정적인 것처럼 들릴 수 있음, "acknowledge"를 사용하면 좀 더 중립적이고 양호한 뉘앙스)

~할 가능성이 높다/ 낮다 : (not) likely , It's likely that ~

 
Likely : 원래 문장에 섞어서 쓴다고 생각하면 됨
 
오늘 밤에는 차가 많이 막힐거야  ---> 오늘 밤에 차가 많이 막힐 가능성이 높아

☞ The traffic will be(is gonna) bad tonight 
 The traffic is likely going to be bad tonight.
 
오늘 밤에는 차가 많이 막히지 않을 가능성이 높아
 
 The traffic is not likely going to be bad tonight.
 
It's likely that ~ 
 
나중에 걔는 생각을 바꿀 가능성이 높아
 
☞ Later, he is likely going to change his mind.
☞ It's likely that he'll change his mind later
 
내가 걔랑 얘기를 다시 해볼수는 있는데, 걔가 생각이 바뀔 가능성은 별로 없어
 
☞ I could try talking with him again, but he is not likely gonna change his mind
I could try talking with him again, but it's not (very) likely that he'll change his mind
 
A: 휴가를 내기 위해 상사와 대화했어?
B: 응, 했는데. 다시 상사와 대화해볼 수는 있겠지만,
     그는 마음을 바꿀 가능성은 낮을 거야. 바쁜 시즌에 휴가를 내는 걸 매우 엄격하게 다루거든.
A: 그렇구나. 그러면 인사팀에게 말해봐서 도움을 받을 수는 없을까?
B: 좋은 생각이야. 그걸로 한 번 시도해봐야겠어. 제안해줘서 고마워.

A: Did you talk to your boss about taking time off for vacation?
B: Yeah, I did. I could try talking with him again, but he's not likely gonna change his mind.
    He's pretty strict when it comes to taking time off during the busy season.
A: I see. Well, maybe you could try talking to HR instead and see if they can help you out.
B: That's a good idea. I think I'll give that a try. Thanks for the suggestion.
 
 
 
예약 잡는게어려울거야 ---> 예약 잡는게 어려울 가능성이 높아
 
 It is going to be difficult(hard) to get a resevation.
 It is likely going to be difficult(hard) to get a resevation. 
 
 
걔가 이미 알고있을 수 있는데 "그럴 가능성은 별로없어" (이때 likely 많이 쓰임 문장)
 
  He might already know, but it's(that's) not very likely

 

A: 네 남동생한테 서프라이즈 파티에 대해서 얘기했어?
B: 아니, 안 했어. 이미 알고 있을 수도 있지만, 그 가능성은 별로 높지 않아. 다른 사람이 그에게 말했을 것 같진 않아.
A: 정말이야?  걔 몰라도 되는 거 꽤 잘 알아채잖아 
B: 알아, 그러나 내가 초대된 모두에게 아무것도 말하지 말라고 잘 알려줬어.
     게다가, 걔 요즘 무슨 일이 일어나고 있는지 그렇게 관심을 많이 두고 있지 않은 것 같아.
A: 그렇구나, 네 판단을 믿지뭐. 그냥 파티 전까지 그가 발견하지 않기를 바라자
B: 응, 제발.

A: Did you tell your brother about the surprise party?
B: No, I didn't. He might already know, but that's not very likely.
    I don't think anyone else would have told him.
A: Are you sure? He's pretty good at finding out things that he's not supposed to know.
B: I know, but I made sure that everyone who was invited knew not to say anything.
   Plus, I don't think he's been paying much attention to what's been going on lately.
A: Okay, I trust your judgment. Let's just hope he doesn't find out before the party.
B: Yeah, fingers crossed!
 
  •  I don't think anyone else would have told him. : 이 문장에서 "would"는 과거 시제의 의미로 사용
    (과거의 예상된 상황을 나타냄 ; 즉, 과거의 상황에서 "그에게 말한 사람이 없었을 것"이라는 추측이나 가정)

I doubt that ~ : 성격은 좀 다름 . 가능성이 별로없다라고 할 때만 쓰임 (나의 판단)

 
 
다시 추워질 가능성은 없어 (뉘앙스: 다시 추워질 가능성은 낮다고 봐; 나의 판단)
 
☞ I doubt that it's going to get colder again
A: 이번 주말 날씨가 어떨까요?
B: "Under the weather" 일지도 몰라요.
      몇 일 전까지 날씨가 점점 추워졌기 때문에, 이번 주말도 그렇게 추울 가능성이 높아 보입니다.
A: 그래도 최근에는 봄이 도착한 것 같은 기분이 들어요. 더 추워지기보다는 오히려 더워질 것 같아요.
     제말은 저는 다시 추워질 가능성은 낮다고 봐요
B: "Break the ice"하고 싶긴 하지만, 저는 아직도 날씨가 추워질 것 같다고 생각해요.
     예측은 항상 불확실하기 때문에 "Bundle up" 해서 감기 조심하는 게 좋겠어요.

A: What do you think the weather will be like this weekend?
B: It might be "under the weather".
     Since the weather was getting colder until a few days ago, there's a good chance it'll be cold this weekend too.
A: Still, I feel like spring has arrived lately. I think it will get warmer rather than colder.
     I mean I doubt that it's going to get colder again
B: I want to "break the ice" but I still think it's going to get colder.
     Predictions are always uncertain, so it's better to "bundle up" and be careful not to catch a cold.

 

  • "Under the weather" : "건강이 좋지 않은, 몸상태가 안좋은"
    ex) I'm feeling a bit under the weather today (오늘은 몸 상태가 좋지 않다)
  • "until a few days ago" : "몇 일 전까지는"(어떤 일이 어떤 시점까지 계속되었으나, 그 시점 이후에는 그렇지 않았음을 나타냄)
    ex) I used to live in New York until a few years ago (몇 년 전까지는 뉴욕에 살았었다)
  • Rather than"은 "보다는"이라는 뜻으로, 둘 중 하나를 선택하는 것을 나타내며, 보통 두 가지 중 하나가 다른 것보다 더 확실하거나 우월하다는 것을 나타낼 때 사용
  • "Break the ice": "어색한 분위기를 해소하다"를 의미
     이 문장에서는 날씨에 대한 예측이 다소 낙관적이지 않은 (상반되는 의견) 의견을 얘기하면서 한 말
      ex) I always find it difficult to break the ice at parties (파티에서 어색한 분위기를 해소하기가 어려워요)
  • "Bundle up" : "따뜻하게 입고 겨울철 추위를 대비하다"(추운 날씨에 옷을 여러 겹 입는 것을 의미하며, 몸을 따뜻하게 감싸는 것)
    ex) It's really cold outside, so make sure you bundle up before you go out (밖이 매우 춥기 때문에 나가기 전에 따뜻하게 입고 갈아입어야 해)

 

HAM (High availablity manager)

QNX HAM은 QNX Software Systems에서 개발한 QNX Neutrino 운영 체제의 높은 가용성 모듈입니다.

특히, 시스템의 장애가 발생했을 때 자동으로 문제를 해결하고 서비스를 유지하는 데 도움을 줍니다.

 

RTOS는 임베디드 시스템에 사용되는 작고 경량화된 운영 체제로, 실시간 응용 프로그램의 요구 사항을 충족하는 데 필수적입니다.

그러나 이러한 시스템에서는 장애가 발생하면 시스템 전체 또는 일부 서비스가 중단될 수 있으며, 이는 치명적인 결과를 초래할 수 있습니다

HAM은 이러한 상황을 방지하기 위해 설계되었습니다.

HAM은 여러 노드 간의 서비스 가용성을 모니터링하고, 문제가 발생한 경우 이를 식별하고 대처하는 기능을 제공합니다.

예를 들어, HAM은 서비스를 실행하는 노드에서 문제가 발생한 경우 다른 노드로 서비스를 이전하거나, 문제가 발생한 노드를 다시 시작하거나, 대체 서비스를 시작할 수 있습니다

RTOS에서 HAM은 시스템의 가용성을 향상시키는 데 필수적입니다. 이를 통해 시스템의 안정성을 높이고, 장애 발생 시 복구 시간을 단축시킬 수 있습니다. 또한, HAM은 시스템 관리자가 서비스와 노드의 상태를 모니터링하고, 관리하는 데 도움을 줍니다

 

따라서 QNX HAM은 주로 두 대 이상의 컴퓨터 노드를 가진 클러스터 환경에서 사용됩니다.

즉, 주 서버와 백업 서버(예: 스탠바이 서버) 형태의 네트워크에서 에서 주 서버에 장애가 발생하면 HAM은 백업 서버가 주 서버의 역할을 대신 수행하도록 전환하는 것입니다.

예를 들어, 클러스터 환경에서 데이터베이스 서버가 실행 중이라고 가정해 보겠습니다.
이 데이터베이스 서버는 주 서버와 백업 서버 모두에서 실행됩니다.
HAM은 이 데이터베이스 서버를 모니터링하고, 데이터베이스 서버에 장애가 발생하면 백업 서버에서 데이터베이스 서버의 역할을 수행하도록 전환합니다.
데이터베이스 서버가 실행 중인 동안 HAM은 주 서버와 백업 서버 간의 통신을 유지합니다.
이를 위해 HAM은 각 노드의 상태를 모니터링하고, 데이터베이스 서버의 상태를 주기적으로 확인합니다.
데이터베이스 서버에서 장애가 발생하면 HAM은 이를 감지하고, 백업 서버에 데이터베이스 서버의 역할을 전환합니다.
전환 프로세스는 다음과 같은 단계를 거칩니다.

1. HAM은 백업 서버에 데이터베이스 서버의 복제본을 만듭니다.
2. HAM은 백업 서버에서 데이터베이스 서버의 역할을 시작하도록 구성합니다.
3. HAM은 주 서버에서 데이터베이스 서버의 역할을 중지합니다.

이러한 과정을 통해 HAM은 데이터베이스 서버의 가용성을 유지하며, 클러스터 환경에서 높은 가용성을 보장합니다.

 

HAM은 클러스터 내에서 동작하는 모든 노드가 상호작용하도록 관리합니다.

이를 위해 HAM은 클러스터 노드 간의 통신을 유지하고, 각 노드의 상태를 모니터링하며, 필요한 경우 백업 서버로의 전환을 관리합니다

 

QNX HAM은 높은 가용성을 유지하기 위해 여러 가지 기술을 사용합니다.

예를 들어, HAM은 특정 서비스(예: 데이터베이스 또는 웹 서버)가 중단될 경우 해당 서비스를 다시 시작하도록 구성할 수 있습니다.

또한 HAM은 노드 간의 자동 재구성, 로드 밸런싱 및 클러스터 구성 관리를 지원합니다

 

QNX HAM은 다양한 산업 분야에서 사용됩니다.

예를 들어, 통신, 자동차, 의료 및 방위 산업에서 클러스터 환경에서 높은 가용성을 필요로 하는 애플리케이션을 지원하기 위해 사용됩니다

 

다음은 C++ 기반 ham 을 활용하는 예제 코드입니다.

 

#include <iostream>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
#include <ha/ham.h>

#define MY_SERV_NAME "MyService"

int main(int argc, char *argv[]) {
    // HAM 연결
    HAM_HANDLE_T ham_handle;
    if (ham_connect(&ham_handle) != EOK) {
        std::cerr << "Failed to connect to HAM\n";
        return EXIT_FAILURE;
    }
    
    // MyService 서비스 검색
    HAM_SERVICE_T *service;
    if (ham_find_service(ham_handle, MY_SERV_NAME, 0, &service) != EOK) {
        std::cerr << "Failed to find MyService\n";
        ham_disconnect(ham_handle);
        return EXIT_FAILURE;
    }
    
    // SIGTERM 시그널 핸들러 등록
    signal(SIGTERM, [](int sig) {
        ham_disconnect(ham_handle);
        exit(EXIT_SUCCESS);
    });
    
    // MyService 서비스 모니터링
    while (true) {
        // MyService 서비스 상태 확인
        HAM_STATE_T state;
        if (ham_get_service_state(service, &state) != EOK) {
            std::cerr << "Failed to get MyService state\n";
            ham_disconnect(ham_handle);
            return EXIT_FAILURE;
        }
        
        std::cout << "MyService state: " << state << "\n";
        
        sleep(1);
    }
    
    // HAM 연결 해제
    ham_disconnect(ham_handle);
    
    return EXIT_SUCCESS;
}

 

먼저, ham_connect() 함수를 사용하여 HAM과 연결합니다.
그리고 ham_find_service() 함수를 사용하여 MyService 서비스를 검색합니다.
이후 while문 안에서 ham_get_service_state() 함수를 사용하여 MyService 서비스의 상태를 주기적으로 확인합니다.
SIGTERM 시그널이 발생하면 HAM과의 연결을 해제하고 프로그램을 종료합니다.

 

pulse

 

QNX에서 pulse는 시스템에서 발생하는 이벤트를 동기적으로 처리하기 위한 방법 중 하나입니다.

Pulse는 두 개의 컴포넌트 간에 메시지를 교환하는 방법 중 하나이며, 높은 우선순위를 가지는 이벤트에 대한 처리를 보장합니다

pulse를 이용하여 시스템에서 발생하는 이벤트를 처리할 수 있습니다.

예를 들어, 자동차 제어 시스템에서는 속도, 방향, 브레이크 등의 상태가 변화할 때마다 pulse를 이용하여 이벤트를 처리합니다.

이를 통해 자동차의 상태 변화를 실시간으로 반영할 수 있습니다

 

다음은 C++에서 pulse를 이용하여 이벤트를 처리하는 예제입니다

 

#include <sys/neutrino.h>
#include <iostream>

int main() {
    // pulse 생성
    int chid = ChannelCreate(0);
    struct _pulse pulse;

    // 이벤트 발생
    int code = 0;
    int value = 1;
    MsgSendPulse(chid, getpid(), code, value);

    // 이벤트 처리
    int rcvid = MsgReceivePulse(chid, &pulse, sizeof(pulse), NULL);
    if (rcvid == -1) {
        std::cerr << "Error receiving pulse" << std::endl;
        return 1;
    }

    std::cout << "Received pulse: code=" << pulse.code << ", value=" << pulse.value.sival_int << std::endl;

    // pulse 삭제
    ChannelDestroy(chid);

    return 0;
}
이 예제는 ChannelCreate 함수를 이용하여 pulse를 생성하고, MsgSendPulse 함수를 이용하여 이벤트를 발생시킵니다.
이후, MsgReceivePulse 함수를 이용하여 이벤트를 처리합니다. 이때, pulse에 저장된 정보를 이용하여 이벤트를 처리할 수 있습니다.
pulse는 이외에도 다양한 용도로 사용될 수 있으며, 높은 우선순위를 가지는 이벤트 처리에 특히 유용합니다

 

Signal

 

QNX에서 signal은 프로세스 간에 비동기적으로 이벤트를 전달하기 위한 방법 중 하나입니다.

Signal은 각각의 시그널 번호를 가지며, 시스템에서 발생하는 이벤트를 처리하기 위해 사용됩니다.

QNX에서 signal은 kill() 함수를 이용하여 다른 프로세스에 전송할 수 있습니다.

이 때, 시그널 번호와 함께 전송되며, 이를 이용하여 대상 프로세스에서 해당 이벤트를 처리할 수 있습니다

 

다음은 C++에서 signal을 이용하여 이벤트를 처리하는 예제입니다.

 

#include <signal.h>
#include <unistd.h>
#include <iostream>

void signalHandler(int signum) {
    std::cout << "Received signal: " << signum << std::endl;
}

int main() {
    // 시그널 핸들러 등록
    signal(SIGUSR1, signalHandler);

    // 시그널 전송
    pid_t pid = getpid();
    int sig = SIGUSR1;
    int ret = kill(pid, sig);
    if (ret == -1) {
        std::cerr << "Error sending signal" << std::endl;
        return 1;
    }

    // 이벤트 처리
    // ...

    return 0;
}

 

이 예제는 signal 함수를 이용하여 시그널 핸들러를 등록하고, kill 함수를 이용하여 시그널을 전송합니다.
이후, 시그널 핸들러에서 해당 이벤트를 처리할 수 있습니다.
signal은 다양한 용도로 사용될 수 있으며, 주로 시스템에서 발생하는 이벤트를 처리하기 위해 사용됩니다.
예를 들어, 프로세스 간 통신에서는 signal을 이용하여 이벤트를 전송하고, 해당 이벤트를 처리하는 방식으로 구현됩니다

버퍼(Buffer)

입력 버퍼는 사용자가 키보드나 마우스를 통해 입력한 데이터를 임시적으로 저장하는 메모리 공간입니다.

일반적으로 입력 버퍼는 FIFO(First-In-First-Out) 방식으로 동작합니다.

사용자가 키를 누르면 해당 키의 입력 데이터가 입력 버퍼의 맨 뒤에 추가됩니다.

그리고 프로그램에서 입력 함수를 호출할 때마다, 입력 버퍼의 가장 앞에 있는 데이터가 제거되며, 이 데이터를 함수가 반환합니다.

 

입력 버퍼는 보통 운영체제에서 관리되며, 이를 통해 여러 개의 프로그램이 동시에 입력을 처리할 수 있습니다. 입력 버퍼는 입력 함수를 호출하기 전까지는 입력 데이터를 저장하기 위한 임시적인 저장소일 뿐입니다. 따라서 입력 함수를 호출하면 입력 버퍼에서 데이터가 제거되므로, 같은 데이터를 두 번 이상 읽을 수 없습니다.

 

예를 들어, 사용자가 "Hello"라는 문자열을 입력하고, 프로그램에서 입력 함수를 두 번 호출하는 경우를 생각해보겠습니다

 

#include <iostream>
using namespace std;

int main() {
    char ch1, ch2, ch3, ch4, ch5;
    ch1 = getchar();
    ch2 = getchar();
    ch3 = getchar();
    ch4 = getchar();
    ch5 = getchar();

    cout << "입력한 문자: " << ch1 << ch2 << ch3 << ch4 << ch5 << endl;
    return 0;
}
위 예제 코드에서 사용자가 "Hello"를 입력하면, 입력 버퍼에는 "H", "e", "l", "l", "o", '\n'이 저장됩니다.
이후 getchar() 함수를 다섯 번 호출하면, 입력 버퍼에서 맨 앞에 있는 데이터가 제거되며, 이를 함수가 반환합니다.
따라서, 위 예제 코드에서 출력되는 문자열은 "Hello\n"이 됩니다.
즉, 입력 버퍼는 프로그램이 입력 데이터를 효율적으로 처리할 수 있도록 하는 운영체제의 기능으로,
입력 함수를 호출할 때마다 데이터를 반환하고, 버퍼에서 제거합니다.

 

 

입력 버퍼는 운영체제에서 제공하는 메모리 공간 중 하나로, 일반적으로 커널 내부의 메모리 영역에 위치합니다.

(따라서 프로그램에서는 입력 버퍼에 직접적으로 접근할 수 없습니다)

운영체제는 표준 입력 스트림에서 데이터를 읽어들이고, 이를 입력 버퍼에 저장합니다.

이후 입력 함수가 호출되면, 입력 버퍼에서 데이터를 읽어들이고, 함수가 반환됩니다.

 

입력 버퍼는 보통 운영체제에서 관리되며, 프로그램에서는 이를 직접적으로 조작할 수 없습니다.

대신, 프로그램은 입력 함수를 호출하여 입력 버퍼에서 데이터를 읽어들입니다.

입력 버퍼의 크기는 운영체제나 터미널의 설정에 따라 다르며, 이를 조절할 수 있는 경우도 있습니다.

일반적으로 입력 버퍼의 크기는 몇 바이트에서 몇 킬로바이트까지 가능합니다

입력 버퍼의 크기는 일반적으로 정적으로 결정됩니다.
즉, 프로그램이 실행될 때 버퍼의 크기가 결정되고, 이후에는 변경되지 않습니다.
이는 대부분의 운영체제에서 버퍼의 크기가 고정되어 있기 때문입니다.
버퍼의 크기는 운영체제나 터미널의 설정에 따라 다르며, 보통 수십바이트에서 수천바이트까지 가능합니다.
이 값은 보통 프로그램에서 변경할 수 없으며, 운영체제나 터미널 설정을 변경하여 버퍼의 크기를 조절할 수 있습니다.
또한, 동적으로 버퍼의 크기를 조절하는 방법도 있습니다.
이 경우에는 프로그램에서 직접적으로 버퍼를 관리하여 크기를 조절할 수 있으며, 이를 위해서는 동적 메모리 할당 함수인 malloc, realloc, free 등을 이용하여 메모리를 할당하고 해제해야 합니다.
그러나 이 방법은 일반적으로 복잡하고 오버헤드가 크므로, 버퍼 크기를 미리 결정하는 것이 일반적입니다

 

버퍼와 사용자 메모리

 

입력 함수가 호출되면, 입력 버퍼에 저장된 데이터를 가져와서 스택이나 힙 영역에 저장할 수 있습니다.

이 때, 입력 함수가 반환하는 값은 입력 버퍼에서 가져온 데이터가 저장된 위치를 나타내는 포인터입니다.

프로그래머는 이 포인터를 이용하여 가져온 데이터를 변수나 배열에 저장할 수 있습니다

 

예를 들어, C++에서 std::cin을 이용하여 입력을 받는 경우, 입력 버퍼에 저장된 데이터를 가져와서 std::string이나 char 배열 등의 변수에 저장할 수 있습니다. 이 때, std::cin 함수는 입력 버퍼에서 가져온 데이터가 저장된 위치를 나타내는 포인터를 반환하며, 이를 이용하여 변수에 데이터를 저장합니다

 

표준 입력 스트림(standard input stream)
표준 입력 스트림은 키보드나 파일 등의 입력 소스에서 데이터를 읽어오는 C++ 표준 라이브러리의 입력 스트림 객체입니다.
표준 입력 스트림은 std::cin 객체를 통해 사용할 수 있으며, >> 연산자를 이용하여 데이터를 읽어올 수 있습니다.
예를 들어, int형 변수에 값을 입력받는 코드는 다음과 같습니다.

int num;
std::cin >> num;

표준 입력 스트림은 프로그램에서 입력을 받는 가장 일반적인 방법입니다.
키보드로부터 데이터를 입력받는 경우에는 표준 입력 스트림이 사용되며,
파일로부터 데이터를 입력받는 경우에도 표준 입력 스트림을 이용하여 파일의 내용을 읽어올 수 있습니다.

 

std::cin

 

std::cin은 C++ 표준 라이브러리에서 제공하는 입력 스트림 객체 중 하나로, 표준 입력(standard input)으로부터 데이터를 읽어옵니다. std::cin은 iostream 헤더 파일에 정의되어 있으며,아래와 같이 동작합니다.

 

1. 키보드나 파일 등의 입력 소스에서 데이터를 읽어옵니다. 이때, 키보드로부터 데이터를 입력받는 경우에는 사용자가 Enter 키를 입력할 때까지 입력을 받습니다

2. 입력된 데이터를 입력 버퍼(input buffer)에 저장합니다

3. 프로그램에서 std::cin 객체를 이용하여 데이터를 읽어오는 경우, 입력 버퍼에서 데이터를 읽어옵니다

4. 읽어온 데이터를 변수에 저장합니다. 이때, >> 연산자를 이용하여 데이터를 읽어옵니다.

     >> 연산자는 입력 버퍼에서 공백(space), 탭(tab), 개행(newline) 등을 구분자로 사용하여 데이터를 분리합니다

 

 

std::cin은 입력 버퍼에 데이터가 있으면 즉시 데이터를 읽어옵니다.

따라서, std::cin을 이용하여 입력을 받을 때에는 입력 버퍼가 비어있는지 확인하는 것이 중요합니다.

입력 버퍼가 비어있지 않은 상태에서 std::cin을 이용하여 데이터를 읽어오면, 입력 버퍼에 남아있는 데이터를 읽어올 수 있습니다.

이를 방지하기 위해서는 std::cin을 이용하여 데이터를 읽어오기 전에 입력 버퍼를 비워주는 작업이 필요합니다

 

예를 들어, 다음과 같은 코드는 사용자로부터 정수를 입력받고, 입력된 값을 출력합니다.

 

#include <iostream>

int main() {
    int num;
    std::cin >> num;
    std::cout << "입력된 값: " << num << std::endl;
    return 0;
}

 

이 코드는 std::cin을 이용하여 사용자로부터 정수를 입력받은 후, 입력된 값을 출력합니다.

>> 연산자를 이용하여 데이터를 읽어올 때, 입력 버퍼에 데이터가 없는 경우에는 사용자의 입력을 기다리며,

입력 버퍼에 데이터가 있는 경우에는 바로 데이터를 읽어옵니다

 

getch

 

C++에서 콘솔 입력을 받을 때 버퍼를 이용하지 않고 바로 입력을 받는 방법으로는 "getch()" 함수를 사용하는 방법이 있습니다.

 

getch()" 함수는 conio.h 헤더 파일에 정의되어 있으며, 키보드 입력을 즉시 받아서 처리합니다. 이 함수는 키 입력을 받으면 바로 반환하므로 버퍼를 사용하지 않습니다

 

다음은 "getch()" 함수를 사용하여 콘솔에서 한 글자를 입력받는 예제 코드입니다.

 

#include <iostream>
#include <conio.h>

using namespace std;

int main() {
    char ch = getch();
    cout << "입력한 문자: " << ch << endl;
    return 0;
}

 

위 예제 코드에서 getch() 함수는 사용자로부터 한 글자를 입력받고, 그 결과를 변수 ch에 저장합니다.
그리고 저장된 값을 출력합니다. 이렇게 하면 입력 버퍼를 사용하지 않고 즉시 입력을 받을 수 있습니다.

 

getchar

 

"getchar()" 함수는 stdio.h 헤더 파일에 정의되어 있으며, 표준 입력 스트림에서 한 글자를 읽어서 처리합니다. 이 함수는 버퍼를 사용하지 않으므로 입력을 받을 때마다 바로 처리할 수 있습니다.

 

다음은 "getchar()" 함수를 사용하여 콘솔에서 한 글자를 입력받는 예제 코드입니다.

 

#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    char ch = getchar();
    cout << "입력한 문자: " << ch << endl;
    return 0;
}

 

위 예제 코드에서 getchar() 함수는 표준 입력 스트림에서 한 글자를 읽어서 변수 ch에 저장합니다.
그리고 저장된 값을 출력합니다. 이렇게 하면 입력 버퍼를 사용하지 않고 즉시 입력을 받을 수 있습니다

 

C++에서 콘솔 입력을 받을 때 버퍼를 사용하지 않는 또 다른 방법으로는 "getchar_unlocked()" 함수를 사용하는 방법이 있습니다

"getchar_unlocked()" 함수는 getchar() 함수와 비슷하지만, 내부적으로 동기화를 수행하지 않아서 더 빠르게 입력을 받을 수 있습니다. 이 함수는 일반적으로 멀티스레드 환경에서 사용되지 않으며, 동기화가 필요하지 않은 단일 스레드 환경에서 사용됩니다

다음은 "getchar_unlocked()" 함수를 사용하여 콘솔에서 한 글자를 입력받는 예제 코드입니다.

 

#include <iostream>
#include <cstdio>

using namespace std;

inline char read_char() {
    char c = getchar_unlocked();
    while (c == ' ' || c == '\n' || c == '\r') {
        c = getchar_unlocked();
    }
    return c;
}

int main() {
    char ch = read_char();
    cout << "입력한 문자: " << ch << endl;
    return 0;
}

 

위 예제 코드에서 read_char() 함수는 getchar_unlocked() 함수를 사용하여 한 글자를 읽어서 처리합니다. 이 함수는 입력 버퍼를 사용하지 않으므로 더 빠르게 입력을 처리할 수 있습니다. 함수 내부에서는 입력 받은 값이 공백 문자(' '), 개행 문자('\n'), 혹은 캐리지 리턴('\r')인 경우에는 다시 입력을 받도록 처리합니다.

 

getch()와 getchar()  차이

 

getch()와 getchar() 함수는 모두 콘솔에서 입력을 받는 함수입니다. 그러나 두 함수에는 몇 가지 차이가 있습니다.

 

1. 헤더파일

 

getch() 함수는 conio.h 헤더 파일에 정의되어 있고, getchar() 함수는 stdio.h 헤더 파일에 정의되어 있습니다

 

2. 입력 대기 방식

 

getch() 함수는 사용자가 키를 누르는 즉시 바로 입력을 받아들입니다. 이에 반해 getchar() 함수는 사용자가 입력을 마치고 Enter 키를 누르면 입력을 받아들입니다

 

3. 입력 처리 방식

 

getch() 함수는 입력 받은 키 값을 즉시 반환합니다. 이에 반해 getchar() 함수는 표준 입력 스트림에서 한 글자를 읽어서 반환합니다

 

4. 입력 처리 대상

 

getch() 함수는 콘솔에서 키 입력만을 받아들입니다.

이에 반해 getchar() 함수는 콘솔에서 뿐만 아니라 파일 등에서도 입력을 받아들일 수 있습니다

따라서, getch() 함수는 사용자가 키 입력을 바로 반영하여 빠른 입력 처리가 필요한 경우에 사용되며, getchar() 함수는 문자열 입력을 받아야 하는 경우나, 파일에서 입력을 받아들여야 하는 경우에 사용됩니다

getchar() 함수는 표준 입력 스트림에서 한 글자씩 읽어들입니다. 때문에 두 글자 이상을 입력하고 Enter 키를 누르면 첫 번째 글자만 읽어들이고, 두 번째 글자는 입력 버퍼에 남게 됩니다. 그리고 Enter 키는 새로운 줄 문자('\n')으로 인식되어 입력 버퍼에 추가됩니다.

이후 getchar() 함수를 다시 호출하면 입력 버퍼에 있는 다음 글자를 읽어들이게 됩니다. 만약 입력 버퍼에 다음 글자가 없다면, getchar() 함수는 입력 대기 상태가 됩니다

따라서, getchar() 함수를 두 번 호출하면 두 글자 이상의 입력을 받을 수 있습니다. 다음은 이에 대한 예제 코드입니다

 

#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    char ch1 = getchar();
    char ch2 = getchar();
    cout << "입력한 문자: " << ch1 << ch2 << endl;
    return 0;
}
위 예제 코드에서 첫 번째 getchar() 함수는 입력 버퍼에서 한 글자를 읽어들이고, 두 번째 getchar() 함수는 다음 글자를 읽어들입니다. 그리고 두 글자를 모두 출력합니다.

 

const char* :  C-style 문자열을 나타내는 포인터

여기서 C-styel 문자열이란, 말그대로 C 언어에서 사용하는 문자열 표현 방식으로, Null 종료 문자열(Null-terminated string)을 사용하여 문자열을 표현합니다.

Null 종료 문자열은 문자열의 끝에 항상 널 문자('\0')가 포함된 문자열을 의미합니다.

예를 들어, "Hello"라는 문자열은 다음과 같이 C-style 문자열로 표현할 수 있습니다.

 

char str[] = {'H', 'e', 'l', 'l', 'o', '\0'};

 

 

이렇게 문자열을 표현할 경우 문자열의 끝을 알기 위해 널 문자('\0')을 사용합니다.
이를 이용해 문자열의 길이를 계산하거나, 문자열을 다루는 다양한 함수를 사용할 수 있습니다.

다음은 C-style 문자열에서 자주 사용되는 함수들입니다 (c++에서 사용할 경우, <cstring> 헤더 include 필요)

 

1.  strlen() : 문자열 길이 계산

 

const char* str = "Hello, world!";
int length = strlen(str); // 13

 *공백, 특수문자(, !) 등도 포함. null 은 미포함.

2. strcpy() : 문자열 복사

 

char src[] = "Hello";
char dest[10];
strcpy(dest, src);
// dest는 "Hello"로 설정됩니다.

 

3.  strcat() : 문자열 연결

 

char str1[20] = "Hello";
char str2[20] = "World";
strcat(str1, str2);
// str1은 "HelloWorld"로 설정됩니다.

 

4. strcmp() : 두 문자열 비교

 

char str1[] = "Hello";
char str2[] = "World";
int result = strcmp(str1, str2); // 음수 값이 반환됩니다.

 

5. strchr() : 문자열에서 특정 문자 검색

 

const char* str = "Hello, world!";
char ch = 'o';
const char* ptr = strchr(str, ch); // "o, world!"

 

6. strstr() : 특정 문자열 검색

 

const char* str = "Hello, world!";
const char* sub_str = "world";
const char* ptr = strstr(str, sub_str); // "world!"

 

C-style 문자열은 char 배열 형태로 표현됩니다.

 

#include <stdio.h>
#include <string.h>

int main() {
    char str1[20] = "Hello";
    char str2[] = ", world!";
    strcat(str1, str2);
    printf("%s\n", str1);

    char str3[20];
    strncpy(str3, str1 + 7, 5);
    printf("%s\n", str3);

    int result = strcmp(str1, str2);
    printf("%d\n", result);

    char* ptr = strstr(str1, "world");
    if (ptr != NULL) {
        strncpy(ptr, "Earth", 5);
    }
    printf("%s\n", str1);

    return 0;
}

 

위 예제에서는 strcat(), strncpy(), strcmp(), strstr() 등의 함수를 사용하여 C-style 문자열을 배열로 표현하고 조작합니다.
strcat(str1, str2) : str1 끝에 str2를 이어 붙입니다.
strncpy(str3, str1 + 7, 5) : str1의 8번째 문자부터 5개의 문자를 str3에 복사합니다.
strcmp(str1, str2) : str1과 str2를 비교합니다.
strstr(str1, "world") : str1에서 "world" 문자열을 찾습니다.
strncpy(ptr, "Earth", 5) : ptr에서 시작하는 위치에 "Earth" 문자열의 5개 문자를 복사합니다.
그러면 str1에는 "Hello, world!"가 저장되어 있을 것입니다.
strcat() 함수를 사용하여 ", world!" 문자열을 이어 붙여 "Hello, world!"를 완성합니다.
그 다음 strncpy() 함수를 사용하여 str1에서 8번째 문자부터 5개의 문자를 str3에 복사하고,
strcmp() 함수를 사용하여 str1과 str2를 비교합니다.
마지막으로 strstr() 함수를 사용하여 str1에서 "world" 문자열을 찾고, strncpy() 함수를 사용하여 "Earth" 문자열로 대체합니다.

 

출력결과

 

Hello, world!
world
72
Hello, Earth!

 

이 때, 문자열의 크기를 지정하지 않아도 되며, 필요에 따라 문자열의 길이를 동적으로 할당할 수 있습니다.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char* str = NULL;
    size_t len = 0;
    ssize_t read;

    printf("Enter a string: ");
    read = getline(&str, &len, stdin);

    if (read == -1) {
        printf("Error reading input\n");
        return 1;
    }

    printf("Input string: %s\n", str);

    char* str2 = (char*)malloc(strlen(str) + 1);
    strcpy(str2, str);
    printf("Copied string: %s\n", str2);

    free(str);
    free(str2);

    return 0;
}
위 예제에서는 getline() 함수를 사용하여 키보드로부터 입력을 받습니다.
입력된 문자열의 길이에 따라 필요한 만큼 메모리를 동적으로 할당합니다.
그 다음, strcpy() 함수를 사용하여 할당된 메모리에 문자열을 복사합니다.
getline() 함수는 입력된 문자열의 길이와 할당된 메모리의 크기를 반환합니다.
malloc() 함수를 사용하여 문자열의 길이에 따라 필요한 만큼 메모리를 동적으로 할당합니다.
할당된 메모리에 strcpy() 함수를 사용하여 문자열을 복사합니다.
마지막으로 free() 함수를 사용하여 할당된 메모리를 해제합니다.

출력결과

 

Enter a string: Hello, world!
Input string: Hello, world!
Copied string: Hello, world!

 

C-style 문자열은 C++에서도 사용할 수 있습니다

(다만, C++에서는 문자열을 다루기 위해 string 클래스를 사용하는 것이 더 안전하고 편리합니다)

 

*(참고) getline

getline() 함수는 C++의 표준 라이브러리인 <iostream> 헤더 파일에 선언된 함수로,

파일 스트림 또는 입력 스트림에서 한 줄씩 문자열을 읽어올 때 사용됩니다.

getline() 함수는 기본적으로 세 개의 매개변수를 받습니다.

 

getline(std::istream& input_stream, std::string& str, char delimiter='\n');

 

  • input_stream: 입력 스트림 또는 파일 스트림 객체입니다. std::cin, std::ifstream 등이 될 수 있습니다.
  • str: 읽어온 문자열이 저장될 std::string 객체입니다.
  • delimiter: 읽어올 문자열의 끝을 표시하는 구분자입니다. 기본값은 개행 문자('\n')입니다.

getline() 함수는 입력 스트림에서 delimiter 문자를 만나거나 스트림의 끝에 도달할 때까지 문자열을 읽어 str 객체에 저장합니다.
이 때, delimiter 문자는 입력 스트림에서 제거되지 않고 버퍼에 남게 됩니다.

getline() 함수를 사용하면 키보드나 파일 등에서 한 줄씩 문자열을 읽어와 처리할 수 있습니다. getline() 함수를 이용하면, 사용자가 입력한 문자열을 std::string 객체에 저장할 수 있으며, 입력 스트림을 더 이상 읽지 않을 때까지 문자열을 계속해서 읽어올 수 있습니다.

 



 string :  문자열을 다루기 위해 사용하는 c++  표준 라이브러리 클래스

 

string은 문자열을 동적으로 할당하여 저장하고, 문자열의 길이 정보도 함께 저장합니다.

문자열을 수정하거나 추가하는 작업이 자유롭기 때문에 보다 편리하게 문자열을 다룰 수 있습니다.

또한, string 클래스는 C++ 표준 라이브러리의 일부로 제공되기 때문에 라이브러리에서 제공하는 다양한 기능을 사용할 수 있습니다.

 

std::string 클래스의 내부 구현은 크게 두 가지로 나눌 수 있습니다.

첫 번째는 문자열 데이터를 저장하기 위한 메모리 관리를 위한 부분이며,

두 번째는 문자열을 조작하기 위한 멤버 함수들을 구현하는 부분입니다.

 

첫 번째 부분에서는 문자열 데이터를 저장하기 위한 메모리를 할당하고, 필요에 따라 재할당합니다.

문자열 데이터는 동적으로 할당된 메모리 공간에 저장됩니다.

std::string 클래스는 문자열 데이터의 길이와 할당된 메모리 공간의 크기를 구별하며, 이를 통해 문자열 데이터를 조작할 때 메모리를 효율적으로 사용합니다.

두 번째 부분에서는 문자열을 조작하기 위한 멤버 함수들이 구현됩니다.

이러한 멤버 함수들은 문자열 데이터에 직접 접근하지 않고, 문자열 데이터에 대한 포인터와 길이 정보를 사용합니다.

이를 통해 문자열을 조작하는 과정에서 문자열 데이터의 내부 구조가 변경되더라도 멤버 함수들이 제대로 동작할 수 있습니다.

 

#include <iostream>
#include <string>

int main() {
    std::string str = "Hello, world!";

    // 문자열 데이터에 접근하는 방법
    const char* data = str.data(); // C-style 문자열로 변환된 데이터 포인터 반환
    std::cout << "Data: " << data << std::endl;

    // 문자열 데이터의 길이를 구하는 방법
    size_t length = str.length();
    std::cout << "Length: " << length << std::endl;

    // 문자열 데이터를 조작하는 방법
    str += " Welcome to the world!";
    std::cout << "Modified string: " << str << std::endl;

    return 0;
}
이 예제 코드에서는 std::string 클래스의 객체를 생성하고, 이를 통해 문자열 데이터를 다루는 방법을 보여줍니다.
먼저, 문자열 데이터에 접근하는 방법은 data라는 std::strring 클래스 멤버함수를 통해 할 수 있습니다.
data() 멤버 함수를 통해 내부 문자열 데이터의 C-style 문자열 형태로 변환된 데이터 포인터를 반환합니다.
이 포인터를 이용하면 std::string 클래스가 제공하지 않는 C-style 문자열 함수를 사용할 수 있습니다.

문자열 데이터의 길이를 구하는 방법 또한 std::string 클래스 멤버함수인 length() 통해 구할 수 있습니다.

 

단, 이 함수는 문자열 데이터가 저장된 메모리의 크기와는 다릅니다.

std::string 클래스는 문자열 데이터의 길이와 할당된 메모리 공간의 크기를 구별하여 문자열을 효율적으로 처리합니다.

std::string 클래스의 멤버 함수인 length()는 문자열의 길이를 반환합니다.
이 길이는 문자열 데이터에 포함된 실제 문자 개수를 의미합니다.
하지만 문자열 데이터가 메모리에 저장되는 방식 때문에, 문자열 길이와 실제 메모리 크기는 일치하지 않을 수 있습니다.
std::string 클래스는 동적으로 문자열 데이터를 할당하고 관리합니다.
이 때, 문자열 데이터는 null 종료 문자('\0')로 끝나야 합니다. 따라서 문자열 데이터를 저장하는 메모리 공간의 크기는 문자열 길이보다 1 큽니다. 예를 들어, "hello" 문자열의 경우 길이는 5이지만, 이 문자열을 저장하는 메모리 공간의 크기는 6입니다.
또한, std::string 클래스는 문자열 데이터를 저장하기 위해 동적으로 메모리를 할당합니다.
이 때, 할당되는 메모리의 크기는 문자열 데이터의 길이와는 관련이 없을 수 있습니다.
예를 들어, "hello" 문자열을 저장하기 위해 할당된 메모리 공간이 8바이트인 경우가 있습니다.
이는 메모리 할당의 내부 알고리즘에 따라 달라질 수 있습니다.
따라서, std::string 클래스의 length() 멤버 함수로 얻는 문자열 길이와 실제 문자열 데이터가 저장된 메모리 크기는 일치하지 않을 수 있습니다.
하지만, std::string 클래스는 문자열 데이터의 길이와 메모리 크기를 투명하게 관리하기 때문에, 사용자는 이러한 내부 구현 상세를 신경쓰지 않고 문자열을 처리할 수 있습니다

 

다양한 멤버 함수를 제공하여 문자열 데이터를 조작할 수 있는데, 이 예제에서는 += 연산자를 사용하여 문자열 데이터를 뒤에 추가하는 것을 구현했습니다. 물론 이 외에도 append(), replace(), insert() 등 다양한 멤버 함수를 사용할 수 있습니다.

 

이러한 방식으로 std::string 클래스는 문자열 데이터를 동적으로 할당하고, 문자열을 조작하는 다양한 멤버 함수들을 제공합니다.

내부적으로는 문자열 데이터를 저장하기 위한 메모리 관리와 문자열 조작 함수들의 구현 등이 이루어지며, 이를 통해 문자열 데이터를 효율적으로 처리할 수 있습니다.

 

아래는 string 멤버함수를 활용하여 문자열 데이터를 처리하는 예제입니다.

(문자열을 분리하여 벡터에 저장하고, 저장된 문자열을 역순으로 출력하는 기능 구현)

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

int main() {
    std::string str = "apple,banana,cherry,date,elderberry";

    // 문자열을 구분자로 분리하여 벡터에 저장하는 방법
    std::vector<std::string> vec;
    size_t start = 0, end = 0;
    while ((end = str.find(',', start)) != std::string::npos) {
        vec.push_back(str.substr(start, end - start));
        start = end + 1;
    }
    vec.push_back(str.substr(start));

    // 저장된 문자열을 역순으로 출력하는 방법
    for (auto iter = vec.rbegin(); iter != vec.rend(); ++iter) {
        std::cout << *iter << std::endl;
    }

    return 0;
}
std::string 클래스의 멤버 함수인 find()과 substr()을 활용하여 문자열을 분리하고, 이를 std::vector 컨테이너에 저장합니다.
이후 저장된 문자열을 역순으로 출력합니다.
1) 문자열을 구분자(' , ')로 분리하여 벡터에 저장
문자열에서 , 문자가 있는 위치를 찾아내는 방법은 find() 멤버 함수를 활용합니다.
이 함수는 문자열에서 주어진 문자 또는 문자열을 찾아, 해당 위치를 반환합니다.
find() 함수의 두 번째 인자는 검색을 시작할 위치를 지정하는 인자입니다.
따라서 start 변수에 이전에 찾은 , 문자의 위치를 저장하고, 이를 시작 위치로 지정하여 , 문자를 찾아냅니다.
이렇게 찾은 문자열은 substr() 멤버 함수를 활용하여 추출합니다.
이 함수는 문자열에서 주어진 위치부터 지정한 길이만큼의 문자열을 반환합니다.
2) 저장된 문자열을 역순으로 출력하는 방법
이 예제에서는 std::vector 컨테이너의 멤버 함수인 rbegin()과 rend()를 활용하여 반복자(iterator)를 역순으로 순회합니다.
이렇게 역순으로 순회하면서 저장된 문자열을 출력합니다.
이러한 방식으로 std::string 클래스의 멤버 함수와 std::vector 컨테이너를 활용하여 문자열을 분리하고, 저장된 문자열을 처리하는 다양한 기능을 구현할 수 있습니다.

 

std::string 클래스의 내부 구현에서는 또한 문자열 데이터의 내부 인코딩에 대한 처리도 이루어집니다.

std::string 클래스는 기본적으로 ASCII나 UTF-8 인코딩을 사용하지만, 다른 인코딩을 사용하는 경우에도 멤버 함수를 통해 문자열 데이터를 적절하게 변환할 수 있습니다.

즉, std::string 클래스는 문자열 데이터를 동적으로 할당하고, 문자열을 조작하는 다양한 멤버 함수들을 제공하는 라이브러리 클래스입니다. 내부적으로는 문자열 데이터를 저장하기 위한 메모리 관리와 문자열 조작 함수들의 구현 등이 이루어지며, 이를 통해 문자열 데이터를 효율적으로 처리할 수 있습니다.

1. know: 사실, 정보를 알고 있는 상태.

네 이메일 주소 알아

I know your email address.

네가 어제 뭐했는지 알아

I know what you did yesterday.


2. Understand: 이해하다 이지만 알다로 해석되는 경우 많음.

 

어떤 마음이신지 알아요

☞ I understand how you feel

 

그녀가 왜 그랬는지 알아요. 그런데 그 결정에 동의하진 않아요

 I understand why she did that. But i don't agree with her decision

 

무슨 의민지 이해가 안가요

 I don't understand what it means

 

무슨말인지 이해해요

 I understand what you told.

 

그들이 그 상황에서 왜그랬는지 이해가 안돼요

 I don't understand why they did in that situation.


3. find out: 몰랐던 사실, '정보'를 알게 되다. 그 과정, 변화를 이야기함.

 

난 그걸 어제서야 알았어요

 I found out that (just) yesterday

 

다음 뉴욕행 기차 출발 시간을 알아내야 해

☞ I need to find out the time of the next train to New york

 

경찰은 누가 범인인지 찾고 있어

☞ The police are trying to find out who committed the crime

 

일요일에 그 식당이 영업하는지 알아보자

☞ Let's find out if the restaurant is open on sundays

 

우리는 프로젝트가 일정에 뒤처지는 이유를 알아내야 돼

☞ We need to find out why the project is begind the schedule


4. figure out: 몰랐던 '문제'의 해결책, 방법을 알게 되다. 그 과정,변화를 이야기함."알아냈어"
                        +  "이해하기 위해 노력하는 것"

 

이 가구 조립하는 방법을 이해할 수 없어

I can't figure out how to assemble this furniture

 

우리는 지출을 줄이는 방법을 찾아야 돼

We need to figure out a way to reduce our expenses

 

우리는 여전히 그 문제에 대한 해결책을 찾고있다

☞  We are still trying to figure out a solution to the problem

 

파티에서 뭐 입을지 결정해야돼

I need to figure out what to wear to the party

 

어떻게 사용하는 건지 알았어

 I figured out how to use it

 

이 문제를 어떻게 해결할 수 있는지 알 수 있나요?

 Can you figure out how to fix this problem? 

 

새로운 프로젝트에 대해 무엇을 파악했나요?

 What did you figure out about the new project? 

 

그는 새로운 소프트웨어를 어떻게 사용해야 하는지 아직 알아내지 못했나요?

 Has he figured out how to use the new software yet? 

 

이 맛있는 요리를 어디에서 배웠나요?

 Where did you figure out how to cook this delicious meal? 


5. realize: 몰랐던 문제가 뭔지 깨달아 알다. 그 과정은 머릿속에서 이뤄지는 뉘앙스.

 

문제가 뭔지 (마침내) 알았어

 I (finally) realized what the problem was.

 

I finally find out what the problem was (X)

 

내가 큰 실수 했다는 걸 알았어

I realized that I made a big mistake.

 

우리는 우리 여권을 집에 두고 온 걸 깨달았다.

 We realized that we had left our passports at home. 

 

그가 계속 거짓말을 해왔다는 걸 깨달았나요?

☞ Have you realized that he's been lying to you this whole time? 

 

그는 시험에 실패한 후에 그는 열심히 공부하는 것의 중요성을 깨달았다.

 He realized the importance of studying hard after failing his exams. 

 

이 아름다운 경치가 얼마나 아름다운지 깨달았나요?

 Did you ever realize how beautiful this view is? 

 

*realize 는 머릿속에서만 벌어지는 일; 깨닳음, notice 는 외부와의 접촉 (보고 , 듣고 하는 등)


6. notice: 알리다라는 뜻이 아니고, 뭔가를 보고 특이한 점, 바뀐점을 캐치해 알다. 알아차리다.

 

오늘 학교에서 내친구 봤는데, 머리 잘랐더라구 (머리자른걸 알아챘어)

I saw my friend at school today and I noticed (that) she had gotten a haircut

 

그녀가 새 앨범을 냈는데, 스타일이 좀 바뀌었더라구

She came out(released) with a new album and I noticed (that) her style had changed a little bit

 

오늘 아침에 길에서 친구랑 마주쳤는데 모습이 약간 바꼈더라구

I ran into my friend on the street this morning, and I noticed she looked a little different

 

그녀가 신상 신발 신고있었던걸 알아챘었어

I noticed that she had worn brand new shoes.

 

오늘 커피 많이 마시던데, 어제 잠 잘 못잤니?

☞  I noticed you were drinking a lot of coffee today, did you have trouble sleeping last night?

 

*notify : 알리다;  notification : 알림

 


7. recognize: 알아차리다란 뜻이 아니고, 뭔가를 보고 그게 뭔지, 누구인지 아는것을 의미. "뭔지/누군지 알다"

 

오늘아침에 길에서 누군가가 나한테 인사했는데, 누군지 몰라봤어

 This morning, someone said hello to me on the street, but I didn't recognize him

 

그녀가 되게 친숙한데 누군지는 모르겠어

She looked very familiar, but I couldn't recognize her

 

어제밤에 클럽에서 전 남친을 만났는데, 못알아본체 했어

 I ran into my ex-boy friend at the club last night. but I pretended not to recognize him.

 

당신 남편의 손글씨를 보면, 알아보시겠어요?

☞ If you saw your husband's handwriting, would you recognize it?


8. be aware of~: 어떤것의 존재 여부에 대해 알다.

그런 정책이 있는지 몰랐어요

☞ I wasn't aware of that policy

 

그들은 그런 룰이 있는지 알고있었어요

They were aware of the rules

 

그 당시에 그런 상황에 대해서 인지하지 못하고 있었어요 (모르고 있었어요)

  They were not aware of the situation at the time


9. be familiar with~: ~어떤 사물등에 대해 잘 알다. 잘 숙지하고 있다. "잘 알다"

 

저는 이 주제에 대해 잘 몰라요

☞ I'm not very familiar with this topic

 

이런 상황을 잘 아세요?

☞ Are you familiar with this kind of situation?

 

전 이 분야에 대해 잘 몰라요. 한국 역사에 대해 잘 아세요? 

 I am not familiar with this area. Are you familiar with Korean history?

 

*know well 은 가급적으로 안쓰는 것이 좋고 쓴다면 사람한테만.


10. tell: 말하다는 뜻 외에 판단해서 알다는 의미가 있음. 지식이 아닌 판단에 대한 것."판단하다"

 

뭐라고 쓰여있는지 모르겠어

 I can't tell what it says 

 

*이런 상황에서 I don't know what it says (X) 내 지식에 대해 말하는 것은 어색하므로

 

무슨 차인지 모르겠어.어떻게 다른지 말해줄래?

☞ I can't tell the difference. can you tell me how they are different.

 

이게 뭔지 알겠어? 이게 물인거 같아?

☞ Can you tell what this is ? Do you think it's water ?

 

*Do you know what this is ? 라고 하면 안됨 (지금 이순간에 이거 보고 판단이 되는지 묻는 상황이므로)

 

일란성 쌍둥이를 구분하는게 나한텐 쉽지 않네요

 It's not easy for me to tell identical twins.

 


"알다"를 어떻게 정확하게 사용하는지 마침내 알았어요

제가 잘못된 방법으로 영어를 공부했었다는 것도 깨달았네요

영어로 "알다"라는 표현을 하는 방법이 많은 줄 몰랐네요

놀라운 강의 감사해요

당신은 영어랑 한국어 둘다 잘 아시는 것 같아요

 

I finally found out how to use "알다"orrectly.

I have realized that I studied English in the wrong way.

I wasn't aware there are many ways to express "알다" in English.

Thanks to your amazing lecture.

You look really familiar with English and Korean.

 

 

출처: 유튜브 채널 '라이브 아카데미' 및 해당 영상 댓글 참고

Chain of Responsibility Pattern

객체 지향 디자인 패턴 중 하나로, 요청을 처리하는 객체들을 연결하여 처리의 책임을 분산시키는 패턴입니다.

이 패턴은 일종의 체인으로 구성되어 있으며, 하나의 요청을 받은 객체가 해당 요청을 처리하지 못할 경우,

이를 다음 객체로 전달하여 계속해서 요청 처리를 시도합니다.

이러한 과정에서 요청을 처리할 객체를 찾을 때까지 요청이 계속해서 전달되며, 이를 통해 요청 처리의 책임이 분산됩니다.

 

이 패턴은 보안 검사, 로깅 등의 작업에서 유용하게 사용될 수 있습니다.

예를 들어, 여러 개의 보안 검사를 수행하는 시스템에서, 각 검사를 담당하는 객체들을 체인으로 연결하여 요청이 전달되는 과정에서 필요한 보안 검사를 수행할 수 있습니다. 또한, 이 패턴은 객체 간 결합도를 낮추고, 유연성과 확장성을 높이는 데에도 도움이 됩니다.

 

이 패턴을 구현하기 위해서는, 요청 처리 객체들이 각각의 인터페이스를 구현하고, 요청 처리에 대한 메소드를 구현해야 합니다.

또한, 객체 간의 연결 관계를 설정할 수 있는 방법이 필요하며, 일반적으로는 체인 형태의 구조를 가진 리스트나 연결 리스트를 사용합니다.

여러 객체들이 연결되어 있는 형태이기 때문에, 요청 처리 과정에서 루프를 돌지 않도록 주의해야 합니다. 특히, 요청 처리 객체가 너무 많아지면, 체인의 끝까지 요청이 전달되지 못하거나, 처리 속도가 느려지는 등의 문제가 발생할 수 있으므로, 적절한 객체 수를 유지해야 합니다.

 

예제1)

 

#include <iostream>
#include <string>

class Validator {
public:
    virtual void setNext(Validator* next) = 0;
    virtual void validate(std::string data) = 0;
};

class LengthValidator : public Validator {
public:
    void setNext(Validator* next) override {
        this->next = next;
    }

    void validate(std::string data) override {
        if (data.length() > 10) {
            std::cout << "Data too long!" << std::endl;
            return;
        }

        if (next != nullptr) {
            next->validate(data);
        }
    }

private:
    Validator* next = nullptr;
};

class SymbolValidator : public Validator {
public:
    void setNext(Validator* next) override {
        this->next = next;
    }

    void validate(std::string data) override {
        if (data.find_first_of("!@#$%^&*()") != std::string::npos) {
            std::cout << "Data contains forbidden symbols!" << std::endl;
            return;
        }

        if (next != nullptr) {
            next->validate(data);
        }
    }

private:
    Validator* next = nullptr;
};

class NumberValidator : public Validator {
public:
    void setNext(Validator* next) override {
        this->next = next;
    }

    void validate(std::string data) override {
        if (data.find_first_of("0123456789") != std::string::npos) {
            std::cout << "Data contains numbers!" << std::endl;
            return;
        }

        if (next != nullptr) {
            next->validate(data);
        }
    }

private:
    Validator* next = nullptr;
};

int main() {
    Validator* validator = new LengthValidator();
    validator->setNext(new SymbolValidator());
    validator->setNext(new NumberValidator());

    validator->validate("test123");
    validator->validate("test123!!!"); // Should fail length and symbol validation
    validator->validate("testtesttest"); // Should fail length validation
    validator->validate("testtesttest123"); // Should pass all validations

    return 0;
}

 

이 예제 코드는 문자열의 길이, 특수문자, 숫자 등을 검사하는 간단한 예제입니다.

각 검사를 담당하는 클래스를 구현하고, 각 클래스들을 연결하여 체인을 만들고, 체인 상에서 검사를 수행합니다

 

인터페이스  :  setNext(Validator* next), validate(std::string data)

객체 간의 연결 관계 : 연결 리스트

 

예제2) 파일 업로드 시 보안 검사를 수행하는 시스템

 

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

class SecurityCheck {
public:
    virtual void check(const std::string& file) = 0;
};

class FileExtensionCheck : public SecurityCheck {
public:
    void check(const std::string& file) override {
        if (file.find(".exe") != std::string::npos ||
            file.find(".dll") != std::string::npos ||
            file.find(".bat") != std::string::npos) {
            std::cout << "File extension not allowed!" << std::endl;
            return;
        }

        std::cout << "File extension check passed." << std::endl;
    }
};

class FileSizeCheck : public SecurityCheck {
public:
    void check(const std::string& file) override {
        if (file.length() > 1024 * 1024) {
            std::cout << "File size too big!" << std::endl;
            return;
        }

        std::cout << "File size check passed." << std::endl;
    }
};

class VirusScanCheck : public SecurityCheck {
public:
    void check(const std::string& file) override {
        std::cout << "Scanning for viruses..." << std::endl;
        // perform virus scan here

        std::cout << "Virus scan check passed." << std::endl;
    }
};

class SecurityCheckSystem {
public:
    void addCheck(SecurityCheck* check) {
        checks.push_back(check);
    }

    void runChecks(const std::string& file) {
        for (SecurityCheck* check : checks) {
            check->check(file);
        }
    }

private:
    std::vector<SecurityCheck*> checks;
};

int main() {
    SecurityCheckSystem system;
    system.addCheck(new FileExtensionCheck());
    system.addCheck(new FileSizeCheck());
    system.addCheck(new VirusScanCheck());

    std::string filename = "example.txt";
    system.runChecks(filename);

    return 0;
}

이 예제 코드는 파일 확장자, 파일 크기, 바이러스 검사를 수행하는 간단한 보안 검사 시스템입니다.

각 검사를 수행하는 클래스를 구현하고, 이를 SecurityCheck 인터페이스로 추상화합니다.

그리고 SecurityCheckSystem 클래스에서는 이러한 검사들을 추가하고, 파일에 대해 이러한 검사들을 수행합니다.

이때, SecurityCheckSystem 클래스는 SecurityCheck 인터페이스를 사용하여 다형성을 지원합니다.

 

예제3) 로깅 시스템

 

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

// Abstract base class for loggers
class Logger {
public:
	virtual ~Logger() {}
    virtual void log(const std::string& message, int level) = 0;
};

// Console logger
class ConsoleLogger : public Logger {
public:
    void log(const std::string& message, int level) override {
        std::cout << "Console logger: " << message << " (level " << level << ")" << std::endl;
    }
};

// File logger
class FileLogger : public Logger {
public:
    FileLogger(const std::string& filename) : filename(filename) {}

    void log(const std::string& message, int level) override {
        std::cout << "File logger: " << message << " (level " << level << ") -> " << filename << std::endl;
        // write message to file here
    }

private:
    std::string filename;
};

// Email logger
class EmailLogger : public Logger {
public:
    EmailLogger(const std::string& email) : email(email) {}

    void log(const std::string& message, int level) override {
        std::cout << "Email logger: " << message << " (level " << level << ") -> " << email << std::endl;
        // send email here
    }

private:
    std::string email;
};

// Chain of responsibility class
class LoggerChain {
public:
    LoggerChain(Logger* logger) : logger(logger) {}

    void addLogger(Logger* newLogger) {
        if (nextLogger) {
            nextLogger->addLogger(newLogger);
        } else {
            nextLogger = new LoggerChain(newLogger);
        }
    }

    void log(const std::string& message, int level) {
        if (level <= logLevel) {
            logger->log(message, level);
        }
        if (nextLogger) {
            nextLogger->log(message, level);
        }
    }

    void setLogLevel(int level) {
        logLevel = level;
    }

private:
    Logger* logger;
    LoggerChain* nextLogger = nullptr;
    int logLevel = 0;
};

int main() {
    // Create loggers
    Logger* consoleLogger = new ConsoleLogger();
    Logger* fileLogger = new FileLogger("log.txt");
    Logger* emailLogger = new EmailLogger("admin@example.com");

    // Create chain of responsibility
    LoggerChain chain(consoleLogger);
    chain.addLogger(fileLogger);
    chain.addLogger(emailLogger);

    // Set log level
    chain.setLogLevel(2);

    // Log messages
    chain.log("This is a debug message", 1);
    chain.log("This is an informational message", 2);
    chain.log("This is a warning message", 3);

    return 0;
}

 

이 예제 코드는 로그를 출력하는 Logger 추상 클래스와 이를 구현하는 ConsoleLogger, FileLogger, EmailLogger 클래스를 정의합니다. 그리고 LoggerChain 클래스에서는 이러한 로그를 처리하는 체인을 구성합니다.

LoggerChain 클래스는 Logger 객체를 멤버로 가지며, 체인의 다음 로거를 가리키는 포인터를 가집니다.

addLogger 함수에서는 새로운 로거를 추가하고, log 함수에서는 현재 로거에서 로그를 출력하고, setLogLevel 함수는 로그 레벨을 설정하며, main 함수에서는 각 로거를 생성하고 LoggerChain 클래스를 사용하여 로그 메시지를 출력합니다.

 

 

Console logger: This is an informational message (level 2)
File logger: This is an informational message (level 2) -> log.txt
Email logger: This is an informational message (level 2) -> admin@example.com
File logger: This is a warning message (level 3) -> log.txt
Email logger: This is a warning message (level 3) -> admin@example.com

 

 

이 예제에서는 로그 레벨이 2로 설정되어 있으므로, 레벨이 2 이하인 로그 메시지만 출력됩니다.

또한, LoggerChain 클래스를 사용하여 로그를 처리하는 체인을 구성하여, 여러 로거를 연결하여 로그 메시지를 출력할 수 있습니다.

 

+ Improvement

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

// Abstract base class for loggers
class Logger {
public:
    virtual ~Logger() {}
    virtual void log(const std::string& message, int level) = 0;
};

// Console logger
class ConsoleLogger : public Logger {
public:
    void log(const std::string& message, int level) override {
        if (level <= m_level) {
            std::cout << "Console logger: " << message << std::endl;
        }
    }
    void setLevel(int level) { m_level = level; }
private:
    int m_level = 1;
};

// File logger
class FileLogger : public Logger {
public:
    FileLogger(const std::string& filename) : m_filename(filename) {}
    void log(const std::string& message, int level) override {
        if (level <= m_level) {
            std::ofstream file(m_filename, std::ios_base::app);
            file << "File logger: " << message << " -> " << m_filename << std::endl;
        }
    }
    void setLevel(int level) { m_level = level; }
private:
    std::string m_filename;
    int m_level = 2;
};

// Email logger
class EmailLogger : public Logger {
public:
    EmailLogger(const std::string& email) : m_email(email) {}
    void log(const std::string& message, int level) override {
        if (level <= m_level) {
            std::cout << "Email logger: " << message << " -> " << m_email << std::endl;
        }
    }
    void setLevel(int level) { m_level = level; }
private:
    std::string m_email;
    int m_level = 3;
};

// Logger chain
class LoggerChain {
public:
    void addLogger(Logger* logger) {
        m_loggers.push_back(logger);
    }
    void log(const std::string& message, int level) {
        for (auto logger : m_loggers) {
            logger->log(message, level);
        }
    }
private:
    std::vector<Logger*> m_loggers;
};

// Logging system
class LoggingSystem {
public:
    LoggingSystem() {
        m_loggerChain.addLogger(&m_consoleLogger);
        m_loggerChain.addLogger(&m_fileLogger);
        m_loggerChain.addLogger(&m_emailLogger);
    }
    void setLogLevel(int level) {
        m_consoleLogger.setLevel(level);
        m_fileLogger.setLevel(level);
        m_emailLogger.setLevel(level);
    }
    void log(const std::string& message, int level) {
        m_loggerChain.log(message, level);
    }
private:
    ConsoleLogger m_consoleLogger;
    FileLogger m_fileLogger{"log.txt"};
    EmailLogger m_emailLogger{"admin@example.com"};
    LoggerChain m_loggerChain;
};

// Main function
int main() {
    LoggingSystem loggingSystem;
    loggingSystem.setLogLevel(2);
    loggingSystem.log("This is an informational message", 2);
    loggingSystem.log("This is a warning message", 3);
    return 0;
}

1. 순환 참조 문제 

 

(예제1)

#include <memory>
#include <iostream>

struct Node {
    std::shared_ptr<Node> next;
    int value;
    Node(int val): value(val) {
        std::cout << "Node " << value << " created." << std::endl;
    }
    ~Node() {
        std::cout << "Node " << value << " destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<Node> head = std::make_shared<Node>(1);
    std::shared_ptr<Node> second = std::make_shared<Node>(2);
    std::shared_ptr<Node> third = std::make_shared<Node>(3);
    
    head->next = second;
    second->next = third;
    third->next = head; // 순환 참조
    
    return 0;
}

위 예제에서 Node 구조체는 단순히 value와 다음 노드를 가리키는 next 멤버 변수를 가지고 있습니다.

main 함수에서는 head, second, third 세 개의 노드를 생성하고 next 포인터로 연결합니다.

그런데 third 노드에서 head 노드를 가리키는 것을 볼 수 있습니다.

이로 인해 head가 참조하는 객체의 reference count가 2가 되어 메모리에서 해제되지 않고 유지됩니다.

프로그램을 실행해보면 다음과 같은 출력을 확인할 수 있습니다.

Node 1 created.
Node 2 created.
Node 3 created.
Node 1 destroyed.

 

(예제2)

#include <memory>
#include <iostream>

struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;
    int value;
    Node(int val): value(val) {
        std::cout << "Node " << value << " created." << std::endl;
    }
    ~Node() {
        std::cout << "Node " << value << " destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<Node> head = std::make_shared<Node>(1);
    std::shared_ptr<Node> second = std::make_shared<Node>(2);
    std::shared_ptr<Node> third = std::make_shared<Node>(3);
    
    head->next = second;
    second->prev = head;
    second->next = third;
    third->prev = second;
    third->next = head; // 순환 참조
    
    return 0;
}

이 예제에서는 Node 구조체에 prev라는 멤버 변수를 추가했습니다.

prev는 현재 노드의 이전 노드를 가리키는 weak_ptr입니다.

main 함수에서는 head, second, third 세 개의 노드를 생성하고 next와 prev 포인터로 연결합니다.

이번에도 third 노드에서 head 노드를 가리키는 것을 볼 수 있습니다.

프로그램을 실행해보면 다음과 같은 출력을 확인할 수 있습니다.

Node 1 created.
Node 2 created.
Node 3 created.
Node 1 destroyed.
Node 2 destroyed.
Node 3 destroyed.

세 개의 노드가 모두 생성된 후 main 함수가 끝나면서 head, second, third가 소멸됩니다.

그리고 head가 참조하는 노드는 third에서도 참조되고 있으므로 reference count가 2인 상태로 남아있게 됩니다.

또한 second는 head를 가리키는 prev 포인터를 가지고 있지만 weak_ptr을 사용했기 때문에 head가 소멸됨에 따라 prev 포인터는 무효화됩니다.

이로 인해 Node 1 destroyed., Node 2 destroyed. 메시지가 출력되지 않습니다.

이와 같이 shared_ptr이 더 복잡한 순환 참조 문제를 일으킬 수 있으므로, 이러한 문제가 발생하지 않도록 설계를 잘 해야 합니다.

순환 참조를 방지하기 위해서는 weak_ptr을 사용하거나, shared_ptr 대신 unique_ptr을 사용하거나, 참조를 다른 객체에게 위임하는 방식을 사용하는 등의 방법이 있습니다.

 

2. shared_ptr 에 raw pointer 를 대입할 때 발생하는 문제

 

#include <memory>
#include <iostream>

struct Node {
    std::shared_ptr<Node> next;
    int value;
    Node(int val): value(val) {
        std::cout << "Node " << value << " created." << std::endl;
    }
    ~Node() {
        std::cout << "Node " << value << " destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<Node> head = std::make_shared<Node>(1);
    std::shared_ptr<Node> second = std::make_shared<Node>(2);
    
    head->next = second;
    second->next = head;

    std::shared_ptr<Node> third(head->next.get()); // raw pointer를 대입
    
    return 0;
}

이 예제에서는 third라는 새로운 shared_ptr을 생성할 때, head의 next 포인터를 가리키는 raw pointer를 사용합니다.

이렇게 되면 third와 head는 같은 객체를 참조하게 됩니다. 따라서 head와 second도 같은 객체를 참조하게 됩니다.

프로그램을 실행해보면 다음과 같은 출력을 확인할 수 있습니다.

Node 1 created.
Node 2 created.

두 개의 노드가 생성된 후 main 함수가 끝나면서 head, second, third가 소멸됩니다.

그러나 head와 second는 같은 객체를 참조하고 있으므로 reference count가 2인 상태로 남아있게 됩니다.

이로 인해 Node 1 destroyed., Node 2 destroyed. 메시지가 출력되지 않습니다.

이와 같이 raw pointer를 사용하여 shared_ptr이 같은 객체를 참조하게 되면, 객체가 제대로 해제되지 않는 문제가 발생할 수 있습니다. 이러한 문제를 방지하기 위해서는 raw pointer를 사용하는 것을 지양하고,

가능하면 shared_ptr 또는 weak_ptr을 사용하여 참조 관리를 해주는 것이 좋습니다.

 

3.  std::enable_shared_from_this 를 상속받아야 shared_ptr 의 안전한 사용이 가능한 이유

 

C++11에서 std::enable_shared_from_this를 상속받는 클래스는 해당 객체가 std::shared_ptr에 의해 관리되는 경우, std::shared_ptr가 객체를 안전하게 참조할 수 있도록 해줍니다.

이를 통해 shared_ptr이 안전하게 사용될 수 있는 이유는 다음과 같습니다.

std::shared_ptr은 객체의 reference count를 관리하며, 객체가 참조하는 모든 std::shared_ptr이 소멸될 때 해당 객체도 소멸됩니다. 그러나 이 때 참조하는 모든 std::shared_ptr이 소멸되어 reference count가 0이 되어야만 객체가 소멸되기 때문에, shared_ptr에 대한 순환 참조 문제가 발생할 수 있습니다.

즉, 객체가 참조하는 shared_ptr과 객체 자체를 참조하는 shared_ptr이 서로를 참조하는 순환 참조 문제가 발생할 수 있습니다.

 

std::enable_shared_from_this는 이러한 순환 참조 문제를 해결하기 위해, 해당 클래스에서 shared_from_this() 함수를 제공합니다. 이 함수는 std::shared_ptr의 인스턴스를 생성하면서 해당 객체를 가리키는 포인터를 반환합니다.

이 함수는 해당 객체가 std::shared_ptr에 의해 참조될 때 호출되어 std::shared_ptr의 내부 참조 카운트를 증가시킵니다.

또한 이 함수는 해당 객체가 이미 std::shared_ptr에 의해 관리되고 있지 않은 경우, std::bad_weak_ptr 예외를 발생시킵니다.

따라서, std::enable_shared_from_this를 상속받는 클래스에서 shared_from_this() 함수를 사용하여 객체를 안전하게 참조하면,

순환 참조 문제를 방지하면서 std::shared_ptr에 의해 안전하게 관리될 수 있습니다.

 

< std::enable_shared_from_this >

std::enable_shared_from_this 클래스는 std::shared_ptr에서 객체의 안전한 참조 계수(reference counting)를 관리하기 위한 기능을 제공합니다. 이 클래스는 std::shared_ptr를 사용하여 동적으로 할당된 객체의 참조 계수를 추적하는 데 필요한 일부 메커니즘을 구현합니다.

보통 std::shared_ptr를 사용하여 객체를 참조하는 경우, 해당 객체를 참조하는 모든 std::shared_ptr의 참조 계수가 1씩 증가하고, std::shared_ptr가 객체의 스코프를 벗어나면 참조 계수가 1씩 감소합니다. 이러한 메커니즘은 객체의 안전한 수명 관리를 보장합니다.

그러나 클래스 내부에서 std::shared_ptr의 사용을 제어하기 위해서는 std::enable_shared_from_this 클래스를 상속받아야 합니다. 이를 상속받은 클래스에서는 shared_from_this() 멤버 함수를 사용하여 this 포인터로부터 std::shared_ptr 인스턴스를 생성할 수 있습니다. 이렇게 생성된 std::shared_ptr은 해당 객체를 참조하는 다른 std::shared_ptr 인스턴스와 함께 안전하게 참조 계수를 관리할 수 있습니다.

따라서 std::enable_shared_from_this 클래스는 객체가 안전하게 참조되는 것을 보장하기 위한 용도로 사용됩니다.

#include <memory>
#include <iostream>

class MyClass : public std::enable_shared_from_this<MyClass>
{
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }

    std::shared_ptr<MyClass> getSharedPtr() { return shared_from_this(); }
};

int main()
{
    std::shared_ptr<MyClass> p1(new MyClass());
    std::shared_ptr<MyClass> p2 = p1->getSharedPtr();

    std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 2
    std::cout << "p2 use_count: " << p2.use_count() << std::endl; // 2

    p1.reset();
    std::cout << "p2 use_count after reset p1: " << p2.use_count() << std::endl; // 1

    p2.reset();
    std::cout << "End of program" << std::endl;

    return 0;
}

위 코드에서 MyClass는 std::enable_shared_from_this<MyClass>를 상속받습니다.

그리고 getSharedPtr() 함수는 shared_from_this() 함수를 호출하여 현재 객체를 가리키는 std::shared_ptr을 반환합니다.

main() 함수에서는 p1과 p2라는 두 개의 std::shared_ptr 인스턴스를 생성합니다.

p1은 new MyClass()로 생성된 객체를 가리키고, p2는 p1의 getSharedPtr() 함수를 호출하여 생성된 std::shared_ptr을 가리킵니다.

그리고 p1의 reference count를 출력하면 2가 출력되는 것을 볼 수 있습니다.

이는 p1과 p2가 동일한 객체를 가리키기 때문입니다.

이후 p1을 reset() 메소드를 통해 소멸시키면, p2의 reference count가 1이 되는 것을 확인할 수 있습니다.

마지막으로 p2를 reset() 메소드를 통해 소멸시키면, MyClass 객체도 함께 소멸됩니다.

이때 MyClass 객체의 소멸자가 호출되며, "MyClass destructor" 메시지가 출력됩니다.

 

 

std::enable_shared_from_this를 상속받지 않은 클래스에서 shared_from_this() 함수를 사용하는 예제

#include <memory>
#include <iostream>

class MyClass
{
public:
    MyClass() { std::cout << "MyClass constructor" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructor" << std::endl; }

    std::shared_ptr<MyClass> getSharedPtr() { return shared_from_this(); }
};

int main()
{
    std::shared_ptr<MyClass> p1(new MyClass());
    std::shared_ptr<MyClass> p2 = p1->getSharedPtr();

    std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 1
    std::cout << "p2 use_count: " << p2.use_count() << std::endl; // 1

    p1.reset();
    std::cout << "p2 use_count after reset p1: " << p2.use_count() << std::endl; // 1

    p2.reset();
    std::cout << "End of program" << std::endl;

    return 0;
}

위 코드에서 MyClass는 std::enable_shared_from_this<MyClass>를 상속받지 않습니다. 그리고 getSharedPtr() 함수는 shared_from_this() 함수를 호출하여 현재 객체를 가리키는 std::shared_ptr을 반환합니다.

main() 함수에서는 p1과 p2라는 두 개의 std::shared_ptr 인스턴스를 생성합니다. p1은 new MyClass()로 생성된 객체를 가리키고,

p2는 p1의 getSharedPtr() 함수를 호출하여 생성된 std::shared_ptr을 가리킵니다.

그러나 MyClass 클래스는 std::enable_shared_from_this<MyClass>를 상속받지 않았기 때문에 shared_from_this() 함수가 사용될 때 문제가 발생합니다. 이 예제에서 shared_from_this() 함수를 호출하면, std::bad_weak_ptr 예외가 발생합니다.

따라서 main() 함수에서 p1과 p2의 reference count는 모두 1인 상태이며, 둘 다 reset() 메소드를 호출하면 객체가 제대로 소멸됩니다.

 

4. 사용자 지정 deleter 를 사용

std::shared_ptr에서 사용자 정의 deleter를 지정하여 메모리 할당과 해제를 처리하는 예제입니다.

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int n = 0) : num(n) {}
    void print() { std::cout << "MyClass::num = " << num << std::endl; }
private:
    int num;
};

void custom_deleter(MyClass* p) {
    std::cout << "Custom deleter called" << std::endl;
    delete p;
}

int main() {
    std::shared_ptr<MyClass> sp1(new MyClass(1), custom_deleter);
    std::shared_ptr<MyClass> sp2(new MyClass(2), [](MyClass* p) { std::cout << "Lambda deleter called" << std::endl; delete p; });
    
    sp1->print();
    sp2->print();
    
    return 0;
}

위 예제에서는 std::shared_ptr를 사용하여 MyClass 객체를 생성합니다.

std::shared_ptr의 생성자에서 두 번째 인자로 사용자 정의 deleter를 지정하고 있습니다.

첫 번째 std::shared_ptr는 함수 custom_deleter를, 두 번째 std::shared_ptr는 람다 함수를 사용하여 deleter를 지정하고 있습니다.

custom_deleter 함수는 객체를 삭제하기 전에 "Custom deleter called"라는 메시지를 출력합니다.

람다 함수는 객체를 삭제하기 전에 "Lambda deleter called"라는 메시지를 출력합니다.

따라서, std::shared_ptr를 통해 객체를 참조하다가 std::shared_ptr의 수명이 끝나면 사용자 정의 deleter가 호출되어 객체를 삭제합니다.

 

 

+ Recent posts