728x90
반응형
728x90
반응형
728x90
반응형

고성능 C++ 해시맵(HashMap) 라이브러리 : emhash


1. ✅ 개요

  • emhashC++용 고성능 헤더 전용 해시맵/해시셋 라이브러리입니다.

  • std::unordered_map 보다 빠르고 메모리 효율적인 대안을 제공하며, 다양한 최적화 구현체가 포함되어 있어 성능 및 용도에 따라 선택할 수 있습니다.

  • 주요 특징:

    • 헤더 전용 (header-only)
    • 고속 삽입/탐색/삭제
    • 낮은 메모리 사용량
    • 높은 부하 계수 (load factor) 지원
    • 다양한 환경(Windows/Linux/Mac, GCC/Clang/MSVC)에서의 호환성


2. 🚀 대표 기능

  • insert_unique: 중복 확인 없이 삽입
  • shrink_to_fit: 사용하지 않는 버킷 제거로 메모리 절약
  • reserve: 초기 해시맵 크기 설정으로 리사이징 최소화
  • 다양한 버전(emhash1~emhash8)의 해시맵 구현 제공


3. 🧪 예제 코드

  • 아래는 emhash8::HashMap을 사용하는 간단한 예제입니다:

  • cpp

      // 헤더 파일 포함
      #include "emhash8.h" // 또는 "emhash7.h" 등 다른 버전 사용 가능
      #include <iostream>
      
      int main() {
          emhash8::HashMap<int, std::string> map;
      
          // 값 삽입
          map[1] = "apple";
          map[2] = "banana";
          map[3] = "cherry";
      
          // 값 접근
          std::cout << "map[2] = " << map[2] << std::endl;
      
          // 반복문을 통한 전체 출력
          for (const auto& kv : map) {
              std::cout << kv.first << ": " << kv.second << std::endl;
          }
      
          // 요소 존재 확인
          if (map.find(3) != map.end()) {
              std::cout << "키 3은 존재합니다." << std::endl;
          }
      
          // 요소 삭제
          map.erase(1);
      
          // 전체 요소 수 출력
          std::cout << "남은 요소 수: " << map.size() << std::endl;
      
          return 0;
      }
    


4. 📊 성능

  • 외부 벤치마크에 따르면 emhashGoogledense_hash_map, absl::flat_hash_map, robin_hood::unordered_flat_map 등과 비교해 삽입/탐색/삭제 성능에서 우수한 결과 를 보였습니다.

  • 특히 반복(iteration) 속도와 부하 계수(load factor) 면에서 탁월한 성능을 자랑합니다.

  • 참고: 공식 벤치마크 결과



5. 🧩 설치 및 사용

  • (1) emhash GitHub 저장소에서 소스 다운로드
  • (2) 원하는 구현 버전의 헤더 파일 포함
    • 예: #include "emhash8.h"
  • (3) 컴파일 시 최적화 플래그를 적용하면 성능이 향상됨
    • -O2, -O3, -march=native


6. ✅ 정리

  • emhash는 고성능을 요구하는 C++ 애플리케이션에서 std::unordered_map의 대체재로 고려할 수 있습니다.
  • 헤더 전용이므로 프로젝트에 쉽게 통합할 수 있으며, 다양한 환경에서 안정적인 작동을 보장합니다.
  • 필요에 따라 emhash1부터 emhash8까지 구현체를 시험해보며 가장 적합한 성능 조합을 선택해보시길 권장드립니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

VSCode 확장(extensions) 개발 : TypeScript.vsix 만들기 가이드


1. 개발 환경 준비

  • VS Code 확장을 개발하려면 다음 도구가 필요합니다:
    • Node.js 설치
    • Visual Studio Code 설치
    • 확장 개발 도구 설치:
      • bash

          npm install -g yo generator-code @vscode/vsce
        
      • ⚠️ 오류 발생 시 --force 옵션을 사용할 수 있습니다:

          npm install -g typescript --force
        


2. TypeScript 설치 및 설정

2.1. 전역 설치 (Global)

  • bash

      npm install -g typescript
    

  • 설치 확인:
    • bash

      tsc --version
    

2.2. 로컬 설치 (프로젝트용)

  • bash

      npm install --save-dev typescript
    

2.3. tsconfig.json 초기화

  • bash

      npx tsc --init
    

  • 샘플 설정:
  • json

      {
        "compilerOptions": {
          "target": "ES2020",
          "module": "commonjs",
          "outDir": "dist",
          "rootDir": "src",
          "strict": true
        }
      }
    


3. 확장 템플릿 생성

  • bash

      npx yo code
    

  • 언어: TypeScript 선택
  • 확장 이름, 설명 등 입력
  • 생성된 디렉터리로 이동 후 의존성 설치:
    • bash

        npm install
      


4. 확장 코드 작성 예시

4.1. 디렉터리 구조

  • dir

      my-extension/
      ├── package.json
      ├── tsconfig.json
      ├── src/
      │   └── extension.ts
    

4.2. package.json

  • json

      {
        "name": "my-extension",
        "displayName": "My Extension",
        "publisher": "your-name",
        "version": "0.0.1",
        "engines": {
          "vscode": "^1.70.0"
        },
        "activationEvents": ["onCommand:my-extension.hello"],
        "main": "./dist/extension.js",
        "contributes": {
          "commands": [
            {
              "command": "my-extension.hello",
              "title": "Hello World"
            }
          ]
        },
        "scripts": {
          "vscode:prepublish": "npm run compile",
          "compile": "tsc -p ./",
          "watch": "tsc -w -p ./"
        },
        "devDependencies": {
          "@types/vscode": "^1.70.0",
          "typescript": "^4.7.4",
          "vscode-test": "^1.6.2"
        }
      }
    

4.3. src/extension.ts

  • ts

      import * as vscode from 'vscode';
      
      function sayHello() {
        vscode.window.showInformationMessage('👋 안녕하세요!');
      }
      
      function sayBye() {
        vscode.window.showInformationMessage('👋 안녕히 가세요!');
      }
      
      export function activate(context: vscode.ExtensionContext) {
        const helloCmd = vscode.commands.registerCommand('my-extension.hello', sayHello);
        const byeCmd = vscode.commands.registerCommand('my-extension.bye', sayBye);
        context.subscriptions.push(helloCmd, byeCmd);
      
        return {
          greet: sayHello,
          farewell: sayBye
        };
      }
      
      export function deactivate() {
        console.log("🛑 확장이 비활성화되었습니다.");
      }
    

  • ⚠️ main 항목은 반드시 컴파일된 JS 경로(dist/extension.js) 를 가리켜야 합니다.
    .ts 파일을 직접 실행할 수는 없습니다.



5. 빌드 및 패키징

5.1 컴파일

  • bash

      npm run compile
    
  • src/extension.tsdist/extension.js로 변환됩니다.


5.2 .vsix 패키지 만들기

  • bash

      vsce package
    
  • my-extension-0.0.1.vsix 파일 생성


5.3 로컬 설치 (테스트용)

  • bash

      code --install-extension my-extension-0.0.1.vsix
    
  • 또는 VS Code에서 Extensions: Install from VSIX... 명령을 사용하여 설치합니다.



6. 외부 확장에서 API 호출

  • activate() 함수에서 객체를 반환하면, 다른 확장에서 다음과 같이 사용할 수 있습니다:

  • ts

      const ext = vscode.extensions.getExtension('your-name.my-extension');
      const api = await ext.activate();
      
      api.greet();    // sayHello() 호출
      api.farewell(); // sayBye() 호출
    


7. 정리

  • 이 가이드를 통해 TypeScript 기반의 VS Code 확장을 생성하고 .vsix 파일로 패키징하는 과정을 간단히 소개하였습니다.
  • 직접 확장을 배포하거나, 팀 내에서 배포 전 테스트용으로 사용하실 수 있습니다. 🚀 👽



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

GitHub CLI (gh) 설치 가이드


  • (1) Windows에서 설치
    • 1.1. Winget 사용 (추천)
      • 명령 프롬프트(cmd)나 PowerShell에서 다음 명령어 실행:
      • bash

          winget install --id GitHub.cli
        
    • 1.2. Scoop 사용
      • bash

          scoop install gh
        
    • 1.3. 설치 파일 수동 다운로드


  • (2) macOS에서 설치
    • 2.1. Homebrew 사용 (추천)
      • bash

          brew install gh
        
    • 2.2. MacPorts 사용
      • bash

          sudo port install gh
        


  • (3) Linux에서 설치
    • 3.1. Ubuntu / Debian 계열
      • bash

          sudo apt update  
          sudo apt install gh
        
    • 3.2. Fedora
      • bash

          sudo dnf install gh
        
    • 3.3. CentOS / RHEL
      • bash

          sudo dnf install 'https://github.com/cli/cli/releases/download/v2.0.0/gh_2.0.0_linux_amd64.rpm'
        
      • ※ 위 URL은 예시이며, 실제 버전은 최신 릴리즈 페이지에서 확인하여 교체해 주세요.


  • (4) 설치 확인 방법
    • 설치가 완료되면 터미널에서 다음 명령어로 정상 설치 여부를 확인할 수 있습니다:
    • bash

        gh --version
      



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++ 버전 별 템플릿 타입 유추(Deduction Guide) 기능 구현 비교


1. 개요

  • 이 글에서는 C++98부터 C++20까지 다양한 C++ 표준 버전에서 템플릿 타입 유추(Deduction Guide) 기능을 어떻게 구현할 수 있는지를 비교합니다.
  • 특히 "const char*"이 입력되었을 때 내부적으로 std::string으로 처리되도록 하는 Holder 클래스 예제를 기준으로 각 버전별 코드를 설명합니다.


2. C++98: 템플릿 특수화와 오버로딩으로 구현

  • cpp

      #include <string>
      #include <iostream>
      
      template<typename T>
      class Holder {
      public:
          Holder(T value) {
              std::cout << "Generic Holder: " << value << std::endl;
          }
      };
      
      template<>
      class Holder<std::string> {
      public:
          Holder(const char* value) {
              std::cout << "Specialized Holder<std::string>: " << value << std::endl;
          }
      };
      
      int main() {
          Holder<int> h1(42);
          Holder<std::string> h2("hello");  // 직접 타입 명시 필요
      }
    

  • 자동 유추 기능이 없기 때문에 사용자가 명시적으로 타입을 지정해야 합니다.


3. C++11 / C++14: 헬퍼 함수로 타입 유추 흉내내기

  • cpp

      #include <string>
      #include <iostream>
      
      template<typename T>
      class Holder {
      public:
          Holder(T value) {
              std::cout << "Generic Holder: " << value << std::endl;
          }
      };
      
      // 헬퍼 함수 제작
      
      template<typename T>
      Holder<T> make_holder(T value) {
          return Holder<T>(value);
      }
      
      Holder<std::string> make_holder(const char* value) {
          return Holder<std::string>(std::string(value));
      }
      
      int main() {
          auto h1 = make_holder(42);         // Holder<int>
          auto h2 = make_holder("hello");    // Holder<std::string>
      }
    

  • make_holder()와 같은 헬퍼 함수를 통해 Deduction Guide와 유사한 효과를 낼 수 있습니다.
  • 단, 일반 생성자 방식(Holder h(...))은 사용할 수 없습니다.


4. C++17: 공식 Deduction Guide 도입

  • cpp

      #include <string>
      #include <iostream>
      
      template<typename T>
      class Holder {
      public:
          Holder(T value) {
              std::cout << "Holder<" << typeid(T).name() << ">: " << value << std::endl;
          }
      };
      
      Holder(const char*) -> Holder<std::string>; // Deduction Guide 
      
      int main() {
          Holder h1(42);         // Holder<int>
          Holder h2("hello");    // Holder<std::string>
      }
    

  • 생성자 인자를 기반으로 자동 템플릿 타입 유추가 가능합니다.


5. C++20: requiresconcepts를 통한 조건 기반 유도

  • cpp

      #include <string>
      #include <iostream>
      #include <concepts>
      
      template<typename T>
      class Holder {
      public:
          Holder(T value) {
              std::cout << "Holder<" << typeid(T).name() << ">: " << value << std::endl;
          }
      };
      
      Holder(const char*) -> Holder<std::string>;
      
      template<std::integral T>
      Holder(T) -> Holder<int>;
      
      template<std::floating_point T>
      Holder(T) -> Holder<double>;
      
      int main() {
          Holder h1("hello");   // Holder<std::string>
          Holder h2(10);        // Holder<int>
          Holder h3(3.14f);     // Holder<double>
      }
    

  • requires 또는 concepts를 활용하여 조건에 따라 다른 타입으로 유도할 수 있습니다.


6. 정리

  • 기능 C++98 C++11/14 C++17 C++20
    템플릿 자동 유추 🔸 함수 통해 우회 ✅ 생성자 인자 기반 유도 ✅ + 조건 분기 가능
    타입 유추 (const char*std::string) 수동 특수화 make_* 함수 Deduction Guide Deduction Guide + requires
    코드 간결성 ❌ 복잡 🔸 함수 우회 ✅ 깔끔 ✅ 더 정교함
  • C++ 버전 별로 타입 유추 기능은 계속 진화 중입니다. 😕 😵 🤔 🚀 👽




  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++20 컴파일 타임 함수 : constevalconstinit


1. consteval — 컴파일 타임 전용 함수

1.1 정의

  • consteval함수가 반드시 컴파일 타임에만 실행되어야 함 을 나타내는 키워드입니다.
  • constexpr보다 더 엄격하며, 런타임 호출은 허용되지 않습니다.

1.2 사용 예시

  • cpp

      consteval int square(int x) { // 컴파일 타임 함수
          return x * x;
      }
      
      constexpr int value = square(5); // ✅ 가능: 컴파일 타임 평가
      int runtime = square(5);         // ❌ 오류: 런타임 호출 불가
    


2. constinit — 정적 변수의 컴파일 타임 초기화 보장

2.1 정의

  • constinit은 **정적 저장 기간(static storage duration)**을 가진 변수에 대해, 컴파일 타임에 초기화가 반드시 일어나야 함을 보장합니다.

  • 런타임 초기화를 막아 **정적 초기화 순서 문제(static initialization order fiasco)**를 방지할 수 있습니다.


2.2 사용 예시

  • cpp

      constinit int global_value = 42; // ✅ 컴파일 타임 초기화됨
      
      // 아래는 오류
      // constinit int another = std::rand(); // ❌ rand()는 런타임 평가
    


3. C++20 consteval vs constinit 비교

  • 항목 consteval constinit
    용도 컴파일 타임 전용 함수 정의 정적 변수의 초기화 시점 명시
    적용 대상 함수 정적 저장 기간을 가진 변수
    평가 시점 컴파일 타임에 반드시 호출 컴파일 타임에 반드시 초기화
    런타임 사용 ❌ 불가 ✅ 변수 사용은 가능 (단 초기화는 컴파일 타임)
    대표 장점 컴파일 타임 계산 강제 초기화 순서 오류 방지



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++ SFINAE(Substitution Failure Is Not An Error) : 템플릿 조건부 제어를 위한 핵심 기법

  • C++SFINAE(Substitution Failure Is Not An Error)템플릿 메타프로그래밍(template meta programming)의 핵심 개념 중 하나 입니다.
  • 이는 C++에서 템플릿 인자 치환(substitution) 과정에서 오류가 발생하더라도 그 자체로 컴파일 에러가 되지 않도록 허용 하는 규칙입니다.
  • 대신 컴파일러는 다른 오버로드 후보를 탐색하거나, 템플릿 인스턴스화를 포기하게 됩니다.


1. 핵심 개념 요약

  • 템플릿 인자 대체 시 오류가 발생하면, 컴파일러는 해당 함수 템플릿을 무시하고 다른 후보를 계속 찾는다.

  • 이 규칙 덕분에 다양한 조건부 템플릿 코드 작성이 가능해졌습니다.



2. 간단한 예제

  • cpp

      #include <type_traits>
      #include <iostream>
      
      template<typename T>
      typename std::enable_if<std::is_integral<T>::value, void>::type
      func(T t) {
          std::cout << "정수 타입입니다: " << t << "\n";
      }
      
      template<typename T>
      typename std::enable_if<std::is_floating_point<T>::value, void>::type
      func(T t) {
          std::cout << "실수 타입입니다: " << t << "\n";
      }
    

2.1. 사용 예:

  • cpp

      func(42);      // 정수 타입입니다
      func(3.14);    // 실수 타입입니다
      // func("hello"); → 컴파일 에러 (문자열에 대한 적절한 템플릿 없음)
    

  • 여기서 std::enable_ifbool 조건이 true일 경우에만 타입을 생성하게 하며, false이면 치환 실패(Substitution Failure) 가 발생해 그 오버로드는 무시됩니다.


3. SFINAE 없이 발생할 수 있는 문제

  • 템플릿 인자에 따라 조건부 코드 작성을 하고 싶지만, 타입이 맞지 않아 에러가 나버리면 전체 템플릿 인스턴스가 실패하게 됩니다.
  • SFINAE는 이러한 조건부 인스턴스화를 가능하게 해줍니다.


4. C++20 이후에는 Concepts로 대체 가능

  • C++20부터는 SFINAE 대신 Conceptsrequires 표현식 을 사용하는 것이 더 명확하고 간결합니다.

  • cpp

      template<typename T>
      requires std::integral<T>
      void func(T t) {
          std::cout << "정수 타입입니다: " << t << "\n";
      }
    


5. 자주 사용하는 SFINAE 도구

  • 도구 설명
    std::enable_if 조건에 따라 타입을 정의/배제
    std::is_same 두 타입이 같은지 비교
    std::is_base_of 상속 관계 확인
    decltype(...) + void_t 표현식이 유효한지 검증하는 패턴



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

AIQt 개발을 더 스마트하게 – QodeAssist 소개 및 설치 가이드


  • 대규모 언어 모델(LLM)을 활용하여 코드 자동 완성, 생성, 리팩토링, 문서화 등의 기능을 제공함으로써 Qt 개발자들의 생산성을 대폭 향상시켜 줍니다.
  • 현재는 OpenAI API를 지원하며, 앞으로 다양한 AI 모델과의 통합도 계획하고 있습니다.


1. 주요 기능

  • 코드 자동 완성 : 현재 작성 중인 코드의 맥락을 파악하여 자연스럽고 유용한 코드 제안을 제공합니다.
  • 코드 생성 : 주석 또는 자연어 설명을 기반으로 코드 스니펫을 자동 생성합니다.
  • 코드 리팩토링 : 기존 코드의 구조를 분석하고 개선 방향을 제안하거나 자동 리팩토링을 실행합니다.
  • 자동 문서화 : 함수나 클래스에 대한 주석을 생성하여 코드 가독성과 유지 보수성을 높입니다.


2. 설치 방법

2.1. QodeAssist 소스 코드 다운로드

  • bash

      git clone https://github.com/Palm1r/QodeAssist.git
    

2.2. Qt Creator에서 플러그인 빌드 및 설치

  • Qt CreatorQodeAssist 프로젝트를 엽니다.

  • 프로젝트를 빌드한 후, 생성된 플러그인 파일(.so, .dll 등)을 Qt Creator의 플러그인 디렉토리에 복사합니다.

  • Qt Creator를 재시작합니다.


2.3. 플러그인 활성화

  • 메뉴에서 [도구] → [플러그인] 으로 이동한 후, QodeAssist 플러그인을 활성화합니다.


3. 지원 및 권장 버전

  • QodeAssistQt Creator의 버전에 따라 호환되는 플러그인 버전이 다릅니다:

  • Qt Creator 버전 QodeAssist 호환 버전
    16.0.0 0.5.2 이상
    15.0.1 0.4.8 ~ 0.5.1
    15.0.0 0.4.0 ~ 0.4.7
    14.0.2 0.2.3 ~ 0.3.x
    14.0.1 이하 0.2.2 이하





  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++ 뷰(View) 타입과 소유(Ownership) 타입


1. 소유 타입 (Owning Types)

  • 소유 타입 은 해당 객체가 자원의 소유권 을 가지고 있어, 수명 관리(생성/소멸) 를 책임집니다.
  • 이 타입은 객체가 소멸될 때 자동으로 해당 자원을 해제합니다.

1.1. 대표적인 소유 타입:

  • std::vector<T>
  • std::string
  • std::unique_ptr<T>
  • std::shared_ptr<T>
  • std::list<T> 등 STL 컨테이너 대부분

  • cpp

      std::vector<int> data = {1, 2, 3};  // data가 메모리의 소유권을 가짐
    


2. 뷰 타입 (View Types)

  • 뷰 타입은 자원을 소유하지 않고 참조만 하는 타입입니다.
  • 즉, 자원의 소유권은 다른 객체가 가지고 있으며, 해당 데이터가 존재하는 동안만 유효합니다.

2.1. 대표적인 뷰 타입:

  • T*, const T* → 일반 포인터
  • std::string_view (C++17) → 문자열 뷰
  • std::span<T> (C++20) → 범위 참조
  • std::ranges::subrange (C++20)
  • std::array_view (C++ 비표준)

  • cpp

      #include <string_view>
      #include <iostream>
    
      void print(std::string_view sv) {
         // std::string_view는 std::string이나 char*로부터 
         // 문자열을 얕게 참조하고, 소유하지 않습니다.
      
          std::cout << sv << '\n';
      }
    
      int main() {
          std::string str = "Hello, World!";
          print(str);                    // std::string
          print("Temporary literal");    // const char*
      }
    


3. 소유 타입 vs 뷰 타입 요약 비교

  • 구분 소유 타입 (vector, unique_ptr) 뷰 타입 (span, string_view)
    소유권 O X
    생명주기 관리 직접 관리 외부 자원에 의존
    복사 비용 깊은 복사 (deep copy) 얕은 복사 (shallow copy)
    사용 용도 자원 생성 및 유지 읽기 전용, 임시 접근


4. 사용 시 주의사항 및 팁

  • 함수 파라미터 전달 시, 복사가 불필요하면 std::span 또는 std::string_view와 같은 뷰 타입을 사용하는 것이 성능상 유리 합니다.
  • 하지만 뷰 타입은 수명 관리에 주의 해야 합니다.
  • 참조 대상 객체가 먼저 소멸되면 댕글링 포인터(유효하지 않은 참조) 문제가 발생할 수 있습니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++20 분기 예측 : [[likely]], [[unlikely]]

  • 조건문 에서 자주 실행될 경로와 그렇지 않은 경로를 컴파일러에 알려주는 기능성능 최적화 에 중요한 영향을 미칩니다.
    • 이런 기능을 분기 예측(branch prediction) 이라고 합니다.
  • C++20에서 도입된 [[likely]], [[unlikely]]는 이러한 목적을 위한 표준 기능입니다.
  • 본 글에서는 그 원리와 다른 언어에서의 유사 기능을 비교하여 설명합니다.


1. 분기 예측과 성능의 관계

  • 현대 CPU파이프라인 구조 를 통해 명령어를 미리 읽고 실행 준비를 합니다.
  • 이때 조건문이 나타나면 CPU분기 예측(branch prediction) 을 통해 어떤 경로로 실행될지를 예상하고 다음 명령어를 준비합니다.

  • 예측이 맞으면 성능이 향상되지만, 틀릴 경우 잘못 읽은 명령어들을 모두 폐기 하고 올바른 명령어를 다시 불러야 합니다.
    • 이를 파이프라인 플러시(pipeline flush) 라고 하며, 성능 저하의 원인이 됩니다.


2. C++20[[likely]], [[unlikely]]

  • C++20에서는 분기 가능성을 명시적으로 나타낼 수 있는 두 가지 특성이 도입되었습니다:

  • cpp

      int a = 10;
    
      if ([[likely]] a > 0) {
           // 자주 실행되는 경로
      } else {
          // 거의 실행되지 않는 경로
      }
    

  • [[likely]] : 해당 조건이 자주 참(true) 이 되는 경우
  • [[unlikely]] : 해당 조건이 자주 거짓(false) 이 되는 경우

  • 이 특성은 컴파일러가 기계어 수준에서 분기 구조를 재배치 할 수 있도록 도와주며, CPU가 올바르게 예측할 가능성을 높여줍니다.


3. C++의 기존 방식: __builtin_expect

  • C++20 이전에는 GCCClang 같은 컴파일러에서 다음과 같은 비표준 방식 을 사용했습니다:

  • cpp

      if (__builtin_expect(condition, 1)) {
          // likely
      }
    

  • __builtin_expect(expr, expected)exprexpected가능성이 높다 는 힌트입니다.
  • 1likely, 0unlikely를 나타냅니다.
  • 현재도 성능 최적화를 위해 널리 사용됩니다.


4. 다른 언어에서의 유사 분기 예측 기능

4.1. Rust

  • Rust에는 안정적인 문법은 없지만, core::intrinsics::likelyunlikely라는 내부 함수 가 존재합니다.
  • 아직 unstable이므로 nightly 빌드에서만 사용할 수 있습니다.
  • 일부 외부 crate(branch, likely, unlikely)를 통해 유사 기능을 사용할 수 있습니다.

  • rust

      if likely(x > 0) {
          // 빠른 경로
      }
    

4.2. Zig

  • Zig은 언어 차원에서 likely(condition) / unlikely(condition)을 명확히 지원합니다.
  • 컴파일러는 이를 기반으로 최적화된 분기 코드를 생성합니다.

  • zig

      if (likely(x > 0)) {
          // ...
      }
    

4.3. Go

  • Go는 명시적인 분기 예측 힌트를 제공하지 않습니다.
  • 대신, 코드 구조를 통해 성능을 최적화하거나, 조건의 순서를 조정하는 식으로 간접적인 힌트를 줄 수 있습니다.
  • Go 팀은 과거에 likely/unlikely를 논의했지만 언어의 단순성 유지를 이유로 도입하지 않았습니다.

4.4. Java

  • Java 역시 명시적인 분기 힌트 문법은 없습니다.
  • 하지만 HotSpot JVM런타임 프로파일링(JIT 컴파일) 을 통해 자주 실행되는 경로를 자동으로 감지하고 최적화합니다.
  • 개발자가 직접 힌트를 주지는 못하지만, 코드 구조나 조건문의 순서 등이 약간의 영향을 줄 수 있습니다.

4.5. C# (.NET)

  • C#에서도 직접적인 문법은 존재하지 않지만, MethodImplOptions.AggressiveInlining 같은 힌트를 통해 JIT 최적화를 유도할 수 있습니다.
  • 성능에 민감한 코드에서는 분기 구조를 나누거나 메서드를 인라인 처리하는 방식으로 최적화합니다.


6. 요약

  • [[likely]][[unlikely]]는 코드가 어떤 경로로 자주 흐를지를 컴파일러에 알려주는 간단하지만 효과적인 성능 최적화 도구 입니다.
  • C++20에서는 이를 표준으로 제공하며, 다른 언어에서도 유사 기능이 도입되거나, 자동화된 방식으로 동작하고 있습니다.

  • 성능에 민감한 시스템을 다룰 때는 분기 가능성을 고려한 구조 설계 와 함께, 각 언어에서 제공하는 최적화 수단을 적절히 활용하는 것이 중요합니다. 🚀



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++ 테스트 프레임워크에서의 부동소수점(float, double) 근사 비교(Approximate Comparison)


1. C++ 부동소수점 비교 기능 프레임워크

  • 실수(부동소수점) 계산은 계산 과정에서 아주 작은 오차가 누적되기 때문에, == 같은 정확한 비교는 거의 항상 실패합니다.
  • 따라서 C++ 테스트 코드에서는 일정 오차 허용 범위 내의 비교, 즉 근사 비교(Approximate Comparison) 가 필수입니다.


  • 각 프레임워크는 다른 방식으로 오차를 정의하고 비교하므로, 정확한 테스트를 위해 각 기능의 특징을 이해하고 사용하는 것이 중요합니다.


2. 프레임워크별 부동소수점 근사 비교 기능

2.1 doctest: doctest::Approx()

  • cpp

      #include <doctest/doctest.h>
      
      TEST_CASE("예제") {
          double halfpi = 1.5707963;
          REQUIRE(halfpi == doctest::Approx(1.5707963));
      }
    

  • epsilon() 으로 상대 오차 정밀도 조절 가능
  • 기본 epsilon = 1e-12
  • 오차 계산:
    • cpp

        std::fabs(lhs - rhs) <= epsilon × max(|lhs|, |rhs|)
      

2.1.1. 예시 비교 결과

  • 비교값 통과 여부 (기본 설정 기준)
    1.57079629 ✅ 통과
    1.57079631 ✅ 통과
    1.5707964 ❌ 실패 가능성 높음

2.1.2. 정밀도 조절 예

  • cpp

      REQUIRE(1.57079631 == doctest::Approx(1.5707963).epsilon(1e-9));  // 허용
      REQUIRE(1.57079631 == doctest::Approx(1.5707963).epsilon(1e-13)); // 실패 가능
    


2.2 Catch2: Catch::Approx()

  • cpp

      #include <catch2/catch.hpp>
    
      TEST_CASE("Catch2 근사 비교") {
          double val = 1.5707963;
          REQUIRE(val == Catch::Approx(1.5707963));
      }
    

  • 사용법 및 APIdoctest와 거의 동일
  • .epsilon(), .margin()으로 정밀도 조절 가능


2.3. Google Test: EXPECT_NEAR()

  • cpp

      #include <gtest/gtest.h>
    
      TEST(FloatingPointTest, NearTest) {
          double val = 1.5707963;
          EXPECT_NEAR(val, 1.5707963, 1e-9);  // 절대 오차
      }
    

  • 세 번째 인자로 절대 오차를 지정 (1e-9)
  • 상대 오차는 직접 계산해야 함


2.4. Boost.Test: BOOST_CHECK_CLOSE()

  • cpp

      #include <boost/test/unit_test.hpp>
      
      BOOST_AUTO_TEST_CASE(test_close) {
          double val = 1.5707963;
          BOOST_CHECK_CLOSE(val, 1.5707963, 0.0001);  // 0.0001% 허용 오차
      }
    

  • 상대 오차를 퍼센트(%) 로 지정
  • 오차 계산: 100 * |a - b| / max(|a|, |b|)


2.5 Eigen: isApprox()

  • cpp

      #include <Eigen/Dense>
      
      Eigen::VectorXd a(2), b(2);
      a << 1.0, 2.0;
      b << 1.0, 2.000000001;
      
      bool is_close = a.isApprox(b, 1e-9);
    

  • 주로 벡터/행렬 비교에 사용
  • 상대 오차 비교 (기본값 존재)


2.6. C++ 표준 방식 (직접 구현)

  • cpp

      #include <cmath>
      
      double a = 1.5707963;
      double b = 1.57079631;
      double epsilon = 1e-9;
      
      if (std::fabs(a - b) < epsilon) {
          // 근사적으로 같다
      }
    

  • 가장 쉽게 구현하는 방식
  • 테스트 프레임워크 메시지가 없어 불편함


3. 요약

  • 프레임워크 함수 오차 방식 조절 방식 특징
    doctest doctest::Approx() 상대 오차 .epsilon(), .scale() 직관적, Catch2와 유사
    Catch2 Catch::Approx() 상대/절대 혼합 .epsilon(), .margin() 직관적, doctest와 유사
    Google Test EXPECT_NEAR() 절대 오차 세 번째 인자로 오차 간단하지만 유연성 낮음
    Boost.Test BOOST_CHECK_CLOSE() 상대 오차 (%) 퍼센트 지정 오래된 스타일의 매크로
    Eigen isApprox() 상대/절대 오차 두 번째 인자로 오차 벡터/행렬에 적합
    C++ 표준 방식 std::fabs(a - b) < e 절대 오차 직접 구현 메시지 부족, 보조적 사용


4. 정리

  • doctest::Approx()는 작고 빠르면서도 강력한 부동소수점 근사 비교 기능을 제공합니다.
  • Catch2::Approx()와 거의 동일한 인터페이스를 가지며, 상대 오차 기반으로 정밀도 제어가 가능합니다.

  • 보다 복잡한 비교나 절대 오차 비교가 필요한 경우 Google Test, Boost.Test, Eigen 등 다른 프레임워크의 기능을 활용하면 됩니다.
  • 어떤 방식이든, 부동소수점은 == 비교가 아니라 근사 비교가 기본 이 되어야 합니다.
  • 테스트 정확도를 높이고자 한다면, 오차 허용 기준을 명확하게 정의 하고 그에 맞는 도구를 선택하는 것이 중요합니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++11 = default= delete

  • C++11부터 도입된 = default= delete는 클래스 설계의 명확성과 안정성을 높여주는 문법입니다.
  • 이 글에서는 각각의 의미, 사용법, 그리고 예제를 통해 두 문법의 차이와 실전 활용법을 설명드립니다.


1. = default

  • = default컴파일러에게 기본 구현을 자동으로 생성하라고 지시 하는 문법입니다.

  • 사용자가 별도로 구현하지 않아도, 기본 생성자나 소멸자, 복사 생성자 등을 자동으로 만들어 줍니다.

  • cpp

      class Mat3x3 {
      public:
          Mat3x3() = default; // 기본 생성자의 구현 코드를 컴파일러가 자동으로 생성
      };
       
      // Mat3x3::Mat3x3() {
      // } // 직접 구현하는 코드를 생략하여도 됨
    

1.1. 주요 특징
  • C++11부터 사용 가능
  • inline으로 처리되어 성능상 이점
  • 클래스에 사용자 정의 생성자가 있어도 기본 생성자를 강제로 생성 가능
  • 안전하고 간결한 코드 를 작성할 수 있음

1.2. 비교 정리
  • 선언 형태 의미 자동 생성 직접 구현 필요
    Mat3x3() = default; 기본 생성자 자동 생성
    Mat3x3(); 선언만 존재 (정의 필요)


2. = delete

  • = delete는 특정 함수나 연산자의 사용을 금지 할 때 사용하는 문법입니다.
  • 이로써 실수로 객체를 복사하거나 특정 타입의 함수를 호출하는 것을 컴파일 단계에서 차단 할 수 있습니다.

  • cpp

      class MyClass {
      public:
          MyClass() = default;
          
          MyClass(const MyClass&) = delete; // 복사 생성자 연산자 금지
          MyClass& operator=(const MyClass&) = delete; // 복사 대입 연산자 금지
      
          void sayHello() const {
              std::cout << "안녕하세요!\n";
          }
      }; 
      
      int main() {
          MyClass a;
          a.sayHello();
      
          // MyClass b = a;      // ❌ 복사 생성자 금지
          // MyClass c;
          // c = a;              // ❌ 복사 대입 금지
      }
    

2.1. 주요 특징
  • C++11부터 사용 가능
  • 컴파일 타임에서 금지 사항을 강제
  • API 설계 시, 의도하지 않은 사용을 예방


3. = delete의 다른 예시: 함수 오버로드 제한

  • cpp

      void func(int) {
          std::cout << "정수 버전 함수입니다.\n";
      }
      
      void func(double) = delete;  // 실수 타입 금지
      
      int main() {
          func(42);      // ✅ 정상
          // func(3.14); // ❌ 컴파일 에러 (double 금지)
    
  • 이처럼 = delete특정 타입이나 동작을 명확하게 차단 하고자 할 때 유용하게 쓰입니다.


  • 참도로 func(double) 타입 이외 다음과 같은 경우에도 암시적 변환 이 가능합니다.
    • cpp

        char c = 'A';
        float f = 3.5f;
        bool b = false;
        short s = 42;
        
        enum Color { RED, GREEN, BLUE };
        Color color = GREEN;
        
        func(c);       // char → int
        func(f);       // float → int
        func(b);       // bool → int
        func(s);       // short → int		 
        func(color);   // enum → int
      


4. 요약

  • 문법 도입 버전 역할 주요 용도
    = default C++11 자동 기본 구현 생성 생성자, 소멸자, 복사/이동 생성자 등
    = delete C++11 특정 함수/연산자 사용 금지 복사 금지, 타입 제한, 안전한 API
  • C++11 이후부터는 이 두 문법을 적극적으로 활용하면, 보다 명확하고 안전한 클래스 설계 가 가능합니다.

  • 코드를 짧고 명확하게 유지하면서, 의도하지 않은 사용을 방지하는 데 큰 도움이 됩니다.




  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형
728x90
반응형

C++ 예외 처리(exception) 비결정(non-deterministic)

  • C++은 강력한 예외 처리 메커니즘을 제공하지만, 현실의 실행 환경 이나 컴파일러의 최적화, 라이브러리 구현 등에 따라 비결정적(non-deterministic) 동작을 보일 수 있습니다.
  • 특히 예외 처리 흐름에서 발생할 수 있는 비결정성의 원인, 그리고 표준 라이브러리 사용 시 어떤 연산이 예외를 던질 수 있는지 확인하는 방법 은 실무에서 매우 중요한 주제입니다.


1. 예외 처리 자체는 결정적이다

  • C++의 예외 처리(try, throw, catch)는 언어적으로는 결정적입니다. 즉, 동일한 조건에서 동일한 예외가 발생하고 처리되어야 합니다. 그러나 다음과 같은 외부 요인이 개입될 경우, 실행 결과가 예측 불가능해지는 비결정성이 발생할 수 있습니다.


2. 예외 처리에서 비결정성이 발생하는 주요 사례

2.1. 스택 풀림(Stack Unwinding) 도중 소멸자에서 예외 발생

  • cpp

      #include <iostream>
      #include <stdexcept>
      
      struct A {
          ~A() { std::cout << "A 소멸자\n"; }
      };
      
      struct B {
          ~B() { throw std::runtime_error("B의 소멸자에서 예외!"); }
      };
      
      int main() {
          try {
              A a;
              B b;
              throw std::runtime_error("기본 예외 발생!");
          } catch (...) {
              std::cout << "예외 잡힘\n";
          }
      }
    

  • 스택 풀림(Stack Unwinding) 과정에서 두 번째 예외가 발생하면, 표준은 std::terminate()를 호출하도록 규정하고 있습니다.

  • 어떤 객체의 소멸자가 먼저 호출될지 명확하지 않아 결과가 플랫폼이나 최적화 수준에 따라 달라질 수 있습니다.



2.2. 멀티스레드(multi-thread) 환경에서의 예외 발생

  • cpp

      #include <iostream>
      #include <thread>
      
      void worker(bool shouldThrow) {
          if (shouldThrow) {
              throw std::runtime_error("스레드 내 예외 발생!");
          }
      }
      
      int main() {
          try {
              std::thread t(worker, true);
              t.join();
          } catch (const std::exception& e) {
              std::cout << "메인 스레드에서 예외 잡힘: " << e.what() << '\n';
          }
      }
    

  • 위 코드는 예외가 메인 스레드에서 잡히지 않습니다!
    • 예외는 서브 스레드(sub thread)에서 발생했기 때문입니다.
  • std::thread는 예외를 전파하지 않기 때문에, 예외를 처리하려면 std::promise, std::future, 또는 커스텀 핸들러를 사용해야 합니다.
  • 예외 발생 시점, 예외 전파 경로, 프로그램 종료 여부 가 실행 환경마다 다를 수 있어 비결정적 동작 을 유발합니다.


2.3. 인라인 함수(inline function)와 비인라인 함수에서의 예외 처리 차이

  • cpp

      #include <iostream>
      #include <stdexcept>
      
      inline void throw_inline() {
          throw std::runtime_error("인라인 예외");
      }
      
      void throw_noninline() {
          throw std::runtime_error("비인라인 예외");
      }
      
      int main() {
          try {
              throw_inline();  // 또는 throw_noninline();
          } catch (const std::exception& e) {
              std::cout << "예외: " << e.what() << '\n';
          }
      }
    

  • 인라인 함수 (inline function)는 예외 발생 시 디버거에서 함수 이름이 스택 트레이스에 보이지 않거나, 최적화에 의해 예외 경로가 사라질 수도 있습니다.

    • 인라인 함수는 호출 오버헤드가 없지만, 예외가 발생할 경우 오히려 복잡한 인라인 코드 흐름 때문에 예외 전파 비용이 증가할 수도 있습니다.
    • 비인라인 함수는 함수 경계를 기준으로 예외 전파가 더 단순함.
  • 컴파일러와 최적화 옵션에 따라 인라인 함수의 예외 경로가 달라져 비결정성이 발생 할 수 있습니다.

    • GCC, Clang, MSVC 등은 인라인 최적화 방식이 다릅니다.
    • 동일한 소스 코드라도, 인라인 예외 처리 방식이 달라 예외 발생 여부, 메시지, 스택 구조가 플랫폼마다 다르게 보일 수 있음 → 완벽한 비결정성


2.4. 표준 라이브러리 구현에 따라 달라지는 예외 처리

  • cpp

      #include <iostream>
      #include <vector>
      
      int main() {
          std::vector<int> v = {1, 2, 3};
      
          try {
              int x = v.at(10);  // 예외 발생 (범위 초과)
              std::cout << "x: " << x << '\n';
          } catch (const std::out_of_range& e) {
              std::cout << "예외 발생: " << e.what() << '\n';
          }
      
          // operator[]는 예외를 던지지 않고, 정의되지 않은 동작 발생 가능
          int y = v[10];  // 런타임에 따라 충돌할 수도 있고, 아닐 수도 있음
          std::cout << "y: " << y << '\n';  // 비결정적 결과 가능
      }
    

  • v.at(n)은 예외를 보장하지만, v[n]은 범위를 벗어나면 예외가 아닌 정의되지 않은 동작(undefined behavior) 을 일으킵니다.
  • 구현에 따라 v[10]충돌(segfault)하거나, 쓰레기 값을 반환하거나, 정상적으로 실행될 수도 있습니다.
  • 릴리즈/디버그 모드, 컴파일러 구현, 플랫폼에 따라 결과가 달라지는 대표적인 비결정적 예제 입니다.

2.4.1. noexcept를 사용한 컴파일 타임 검사

  • cpp

      #include <vector>
      #include <type_traits>
      
      int main() {
          std::vector<int> v;
      
          static_assert(noexcept(v.size()), "예외 발생 가능!");
          bool b1 = noexcept(v.size());         // true
          bool b2 = noexcept(v.at(10));         // false
          bool b3 = noexcept(v.push_back(5));   // false
      }
    

  • noexcept(expr)expr이 예외를 던지지 않으면 true, 그렇지 않으면 false를 반환합니다.
  • 템플릿 코드에서 정적으로 예외 가능 여부를 검사하는 데 유용합니다.

2.4.2. cppreference.com 등 공식 문서 활용

  • C++ 표준 라이브러리 함수는 cppreference.com 에서 예외 명세 를 확인할 수 있습니다.

    • 예: std::vector::at()"Throws: std::out_of_range"로 명시됨.
  • noexcept 여부도 문서에 명확히 표시되어 있습니다.


2.4.3. IDE나 툴의 자동 완성 기능

  • Visual Studio, CLion, VSCode+Clangd 등에서는 함수 시그니처에 noexcept가 표시됩니다.
  • 예외 여부를 빠르게 확인할 수 있어 실무에서 자주 활용됩니다.

2.4.4. 사용자 정의 타입과 예외 전달

  • cpp

      #include <vector>
      #include <utility>
      
      struct MyType {
          MyType(MyType&&) noexcept {}  // 예외 없음
      };
      
      int main() {
          std::vector<MyType> v;
          static_assert(noexcept(std::vector<MyType>(std::move(v))), "예외 발생 가능!");
      }
    

  • 컨테이너는 내부에서 타입의 복사/이동 생성자에 noexcept가 붙어 있어야 예외 안전성을 보장할 수 있습니다.
  • 사용자 정의 타입의 예외 가능성이 STL 컨테이너의 예외 처리 방식에 영향을 줄 수 있습니다.


3. 정리

항목 설명
C++ 예외 처리 언어적으로는 결정적이나 외부 요인에 따라 비결정성 발생 가능
비결정성 원인 인라인 최적화, 멀티스레드, 구현 의존성, 소멸자 중첩 예외
예외 가능성 확인 noexcept, cppreference 문서, IDE 툴팁, static_assert 활용 가능
  • C++ 예외 처리는 강력하지만 복잡합니다.
  • 특히 실전에서는 비결정성으로 인해 예외가 잡히지 않거나 원인을 찾기 어려운 상황이 자주 발생합니다.
  • 따라서 STL 함수의 예외 가능성 여부를 정적으로 검사하고, 신중하게 예외 경로를 설계하는 습관 이 중요합니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄

728x90
반응형

+ Recent posts