개념

  • 값의 크기를 직접 세서 정렬하는 알고리즘
    • 비교 기반이 아닌 정렬
  • 값이 정수 범위 내에 한정되어 있을 떄, 빠르게 동작
  • 범위가 작고 양의 정수일 떄 사용

사용방식

  • 가장 큰 정수 값을 찾음
  • 찾은 정수 값 까지의 정수 배열을 생성
  • 각 값이 몇 번 등장했는지 배열에 기록

주의 사항

  • 메모리를 많이 먹기에 (가장 큰 값 만큼의 배열 크기), 값 범위가 작을 떄 사용

시간 복잡도

상황 시간
전체 데이터 스캔 O(N)
카운트 배열 작성 O(K)
누적합 계산 O(K)
결과 정렬 생성 O(N)

'공부 > 알고리즘공부' 카테고리의 다른 글

Radix Sort  (0) 2025.04.29
2진수의 사칙연산  (0) 2025.03.01
정렬알고리즘 #단순정렬  (0) 2022.07.11
In-place & Stable ?  (0) 2022.05.01
Two Point 알고리즘  (0) 2022.04.29

개념

  • 숫자의 자릿수별로 차례대로 정렬해 나가는 방법
    • 비교 기반이 아닌 정렬
  • 한 자리씩 (1의 자리, 10의 자리, 100의 자리...) 순서대로 정렬
  • 각 자릿수 정렬은 주로 Counting Sort를 사용해서 안정적으로(stable) 처리

사용방식

  • 가장 작은 자리수부터 정렬 (LSD: Least Significant Digit)
  • Counting Sort로 해당 자리만 기준으로 정렬
  • 다음 자리수로 이동해서 반복

사용처

  • 수가 작은 정수일 때 (자릿수 작음)
  • 문자열 길이가 고정돼 있을 때
  • ID 번호, 학번처럼 포맷이 일정한 데이터
  • ※ 일반 정렬이 더 나을 수 있으니 남용 x {문자 길이 우선 정렬 등 <- 문자열 길이가 일관x}*

주의 사항

  • Counting Sort 메모리 소모 주의

시간 복잡도

단계 시간
각 자리별 정렬 O(N + K)
자리 수 (d자리)만큼 반복 d번

'공부 > 알고리즘공부' 카테고리의 다른 글

Counting Sort  (0) 2025.04.29
2진수의 사칙연산  (0) 2025.03.01
정렬알고리즘 #단순정렬  (0) 2022.07.11
In-place & Stable ?  (0) 2022.05.01
Two Point 알고리즘  (0) 2022.04.29

핵심 원리

  • vector의 uinique({start}, {end})erase({start},{end}) 의 조합 사용

주의사항

  • uinique정렬된 vector에만 사용
  • unique는 중복 값을 지우고 생긴 쓰레기 값 연속의 가장 앞에 있는 곳을 가르키는 이터레이터 반환
    • erase로 해당 이터레이터 시작 ~ 컨테이너의 끝 구간을 지워야 함. (뒤에 쓰레기 값 연속이 남아 있기 떄문)

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

union  (0) 2025.04.10
함수 포인터  (0) 2025.04.09
(cpp17)string_view  (2) 2025.04.09
예외처리(simple)  (0) 2025.04.09
Attribute  (0) 2025.03.27

핵심 원리

  • 어떤 수 N 의 자리 수는 "그 수의 로그를 취한 값" 임을 이용
  • 10 진수는 $log_{10}(N)$
  • 따라서, $log_{10}(2) \approx 0.301$
  • 즉, $1 / 0.301 \approx 3.3$ 이 되므로 10진수 자리수 ≈ floor(비트 수 × 0.301) + 1

결론

  • 2진수의 비트 수 / 3.3
    • 오차율 적은 임의 값 3.3
  • 10진수 자리수 ≈ floor(비트 수 × 0.301) + 1
    • 정석

실제 대응표

비트 수 계산 (n × 0.301) floor 후 +1 실제 최대값 (2ⁿ⁻¹) 십진수 자리수
8비트 8 × 0.301 ≈ 2.408 floor(2.408) +1 = 3 255 3자리
16비트 16 × 0.301 ≈ 4.816 floor(4.816) +1 = 5 65,535 5자리
24비트 24 × 0.301 ≈ 7.224 floor(7.224) +1 = 8 16,777,215 8자리
31비트 31 × 0.301 ≈ 9.331 floor(9.331) +1 = 10 2,147,483,647 10자리
64비트 64 × 0.301 ≈ 19.264 floor(19.264) +1 = 20 18,446,744,073,709,551,615 20자리

'공부 > 수학' 카테고리의 다른 글

코사인 유사도 ( 벡터 유사성 판별 )  (0) 2024.05.22

for문에 auto& 순회를 통한 삭제 시 발생

  • 해당 for문 삭제 시에는 별다른 이슈 증상 존재 x
    • 사이즈 감소에 문제 x
    • range out 문제 발생 x
  • 해당 컨테이너를 다시 사용할 떄 문제 발생
    • 디버그 시, 컨테이너의 size가 정상 감소 하였으나
    • 다시 한 번더 사용할 떄에, 댕글링 포인터 객체가 남아있는 것을 확인 가능

문제 발생 이유

  • auto& 의 경우, 직접 참조를 하는 방식이기 떄문에 erasedelete
    를 사용하는 삭제 작업 시에 반복자 무효화{구조가 변하면, 이미 순회 사용 중인 반복자가 더 이상 유효하지 않게되는 현상}로 인해 상태가 이상해 질 수 있다
  • 순회 종료 후, 컨테이너 내부 데이터가 깨져서 정상적인 확인이 불가능
    • 디버그 시에도 정확한 원인을 찾기가 어려움
  • STL의 컨테이너에 대한 이해 부족으로 auto&의 for 삭제를 허용한다고 착각

어떻게 대처?

  • auto& 를 사용한 직접 참조를 사용하여 컨테이너에 삽입, 삭제등을 하는 경우에는 반드시 iterator를 통한 반복 순회를 통해서 사용해야 함을 사용자가 처리!!!
  • 또한, 가급적 auto& 를 사용한 for문 방식
    보다 iterator를 사용한 for문 방식을 사용하는 것이 디버그 처리에 좋다.

해당 코드

  • 잘못된 코드
      // 디버그 중단점으로 확인 시에는 문제 확인이 불가    
      for (auto& [entity, bar] : uiMopHPBars) {
          // 내부에서 erase()
          uiMopHPBars.erase(entity);  // 반복자 무효화 발생 (UB)
      }
  • 올바른 사용 코드
    auto it = map.begin();
    while (it != map.end()) {
      if (it->first == 1) {
          it = map.erase(it);
      } else {
          ++it;
      }
    }
    // or
    for (auto it = map.begin(); it != map.end(); ++it)
    {
      if (it->first == 1) { it = map.erase(it); }
      else { ++it; }
    }

별첨

  • c++20 이상의 erase_if

    • 조건을 만족하는 요소를 찾아 자동 삭제

      std::erase_if(container, [](const auto& value) {
        return 조건;
      });
  • c++17 용 커스텀

      template<typename Container, typename Predicate>
      void erase_if(Container& container, Predicate pred)
      {
          for (auto it = container.begin(); it != container.end(); )
          {
              if (pred(*it))
                  it = container.erase(it);
              else
                  ++it;
          }
      }
    
      std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
      erase_if(numbers, [](int x) { return x % 2 == 0; });

목차

  • [[#7.1 프레임 자원|7.1 프레임 자원]]
  • [[#7.2 렌더 항목|7.2 렌더 항목]]
  • [[#7.3 패스별 상수 버퍼|7.3 패스별 상수 버퍼]]
  • [[#7.6 루트 서명 추가 설명|7.6 루트 서명 추가 설명]]
    • [[#7.6 루트 서명 추가 설명#7.6.1 루트 매개변수|7.6.1 루트 매개변수]]
    • [[#7.6 루트 서명 추가 설명#7.6.6 루트 인수의 버전 적용|7.6.6 루트 인수의 버전 적용]]

7.1 프레임 자원

  • "Ring Buffer" 또는 "Frame Resource Cycling" 기법

  • CPU가 자주 갱신해야 하는 자원들을 효율적으로 관리하고 동기화 문제를 피하기 위해 쓰는 대표적 전략

  • GPU와 CPU의 같은 자원 사용 충돌을 피하기 위해, 매 프레임마다 다른 인스턴스를 사용하는 방식

  • 사용자 설정 프레임 자원 생성 예제

      // CPU가 한 프레임의 명령 목록들을 구축하는 데 필요한 자원들을 대표하는
      // 클래스. 응용 프로그램마다 필요한 자원이 다를 것이므로,
      // 이런 클래스의 멤버 구성 역시 응용 프로그램마다 달라야 함
      struct FrameResource
      {
      public:
          FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount);
          FrameResource(const FrameResource& rhs) = delete;
          FrameResource& operator=(const FrameResource& rhs) = delete;
          ~FrameRResource();
    
          // 명령 할당자는 GPU가 명령들을 다 처리한 후 재설정
          // 따라서 프레임마다 할당자가 필요
          Microsoft::WRL::ComPtr<ID3D12CommandAllocator> CmdListAlloc;
    
          // 상수 버퍼는 그것을 참조하는 명령들을 GPU가 다 처리한 후에
          // 갱신. 따라서 프레임마다 상수 버퍼를 새로 만들기
          std::unique_ptr<UploadBuffer<PassConstants>> PassCB = nullptr;
          std::unique_ptr<UploadBuffer<ObjectConstants>> ObjectCB = nullptr;
    
          // Fence는 현재 울타리 지점까지의 명령들을 표시하는 값
          // 이 값은 GPU가 아직 이 프레임 자원들을 사요하고 있는지 판정
          UINT64 Fence = 0;
      }
    
      FrameResource::FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount)
      {
          ThrowIfFailed(device->CreateCommandAllocator(
              D3D12_COMMAND_LIST_TYPE_DIRECT,
              IID_PPV_ARGS(CmdListAlloc.GetAddressOf())));
    
          PassCB = std::make_unique<UploadBuffer<PassConstants>>(device, passCount, true);
          ObjectCB = std::make_unique<UploadBuffer<ObjectConstnas>>(device, objectCount, true);
      }
      FrameResource::~FrameResource() {}
  • 프레임자원 방식 사용 예제

      static const int NumFrameResources = 3;
      std::vector<std::unique_ptr<FrameResource>> mFrameResources;
      FrameResource* mCurrFrameResource = nullptr;
      int mCurrFrameResourceIndex = 0;
    
      void ShapesApp::BuildFrameResources()
      {
          for(int i = 0; i < gNumFrameResources; ++i)
          {
              mFrameResources.push_back(std::make_unique<FrameResource>(
                  md3dDevice.Get(), 1, (UINT)mAllRitems.size()));
          }
      }
    
      void ShapesApp::Update(const GameTimer& gt)
      {
          // 순환적으로 자원 프레임 배열의 다음 원소에 접근
          mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % NumFrameResources;
          mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex];
    
          // GPU가 현재 프레임 자원의 명령들을 다 처리했는지 확인한다. 아직
          // 다 처리하지 않았으면 GPU가 이 울타리 지점까지 명령 처리할 떄까지 대기
          if (mCurrFrameResource->Fence != 0 &&
              mCommandQueue->GetLastCompletedFence() < mCurrFrameResource->Fence)
          {
              HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
              ThrowIfFailed(mCommandQueue->SetEventOnFenceCompletion(
                  mCurrFrameResource->Fence, eventHandle));
              WaitForSingleObject(eventHandle, INFINITE);
              CloseHandle(eventHandle);
          }
    
          // [...] mCurrFrameResource의 자원들 갱신
      }
    
      void ShapesApp::Draw(const GameTimer& gt)
      {
          // [...] 이 프레임의 명령 목록들을 구축하고 제출
    
          // 현재 울타리 지점까지의 명령들을 표시하도록 울타리 값을 전진
          mCurrFrameResource->Fence = ++mCurrentFence;
    
          // 새 울타리 지점을 설정하는 명령을 명령 대기열에 추가
          // 지금 우리는 GPU 시간선 상에 있으므로, 새 울타리 지점은
          // GPU가 이 Signal() 명령 이전까지의 모든 명령 처리 전까지는 설정x
          mCommandQueue->Signal(mFence.Get(), mCurrentFence);
    
          // GPU가 아직도 이전 프레임 명령 처리하고 있을 수 있지만,
          // 연관된 프레임 자원들은 여기서 건드리지 않으므로 문제 x
      }

7.2 렌더 항목

  • 하나의 물체를 그리기 위해 정점 버퍼와 색인 버퍼 묶고, 상수 묶고, 기본 도형 종류 설정 및 DrawIndexedInstanced 매개변수 지정 같은 설정 자료를 캡슐화 하는 경량 구조체를 정의하면 도움됨
  • 하나의 완전한 그리기 호출 명령을 렌더링 파이프라인에 제출하는 데 필요한 자료 집합을 이 책에서 "렌더 항목" 이라 함
  • 렌더 항목 구조체 만들기 예제
// 하나의 물체를 그리는 데 필요한 매개변수들을 담는 가벼운 구조체
// 구체적인 구성은 응용 프로그램마다 상이
struct RenderItem
{
    RenderItem() = defualt;

    // 세계 공간을 기준으로 물체의 국소 공간을 서술하는 세계 행렬
    // 이 행렬은 세계 공간 안에서의 물체의 위치와 방향, 크기를 결정
    XMFLOAT4X4 World = MathHelper::Identity4x4();

    // 물체의 자료가 변해서 상수 버퍼를 갱신해야 하는지의 여부를
    // 뜻하는 '더러움' 플래그. FrameRResource마다 물체의 cbuffer가
    // 있으므로, FrameResource마다 갱신을 적용. 따라서, 물체의 자료를
    // 수정할 때는 반드시 NumFrameDirty = gNumFrameResources 로 설정
    // 그래야 각각의 프레임 자원이 갱신됨
    int NumFramesDirty = gNumFrameResources;

    // 이 렌더 항목의 물체 상수 버퍼에 해당하는
    // GPU 상수 버퍼으 ㅣ색인
    UINT ObjCBIndex = -1;

    // 이 렌더 항목에 연관된 기하구조. 여러 렌더 항목이 같은
    // 기하구조를 참조할 수 있음을 주의
    MeshGeometry* Geo = nullptr;

    // 기본도형 위상구조
    D3D12_PRIMITIVE_TOPOLOGY PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;

    // DrawIndexedInstanced 매개변수들
    UINT IndexCount = 0;
    UINT StartIndexLocation = 0;
    int BaseVertexLocation  = 0;
};

// 모든 렌더 항목의 목록
std::vector<std::unique_ptr<RenderItem>> mAllRitems;

// PSO별 렌더 항목들
std::vector<RenderItem*> mOpaqueRitems;
std::vector<RenderItem*> mTransparentRitems;

7.3 패스별 상수 버퍼

  • HLSL 상수 버퍼 예시

      cbuffer cbPerObject : register(b0)
      {
          float4x4 gWorld; 
      };
    
      cbuffer cbPass : register(b1)
      {
          float4x4 gView;
          float4x4 gInvView;
          float4x4 gProj;
          float4x4 gInvProj;
          float4x4 gViewProj;
          float4x4 gInvViewProj;
          float3 gEyePosW;
          float cbPerObjectPad1;
          float2 gRenderTargetSize;
          float2 gInvRenderTargetSize;
          float gNearZ;
          float gFarZ;
          float gTotalTime;
          float gDeltaTime;
      };
  • 상수 버퍼가 변경되었을 때만 업데이트 하도록

      void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
      {
          auto currObjectCB = mCurrFrameResource->ObjectCB.get();
          for(auto& e : mAllRitems)
          {
              // 상수들이 바뀌었을 때에만 cbuffer 자료 갱신
              // 이러한 갱신을 프레임 자원마다 수행
              if(e->NumFramesDirty > 0)
              {
                  XMMATRIX world = XMLoadFloat4x4(&e->World);
    
                  ObjectConstants objConstants;
                  XMStoreFloat4x4(&objConstants.World, XMMatrixTranspose(world));
    
                  currObjectCB->CopyData(e->ObjCBIndex, objConstants);
    
                  // 다음 프레임 자원으로 넘어가기
                  e->NumFramesDirty--;
              }
          }
      }
    
      void ShapesApp::UpdateMainPassCB(const GameTimer& gt)
      {
          XMMATRIX view = XMLoadFloat4x4(&mView);
          XMMATRIX proj = XMLoadFloat4x4(&mProj);
    
          XMMATRIX viewProj = XMMatrixMultiply(view, proj);
          XMMATRIX invView = XMMatrixInverse(&XMMatrixDeterminant(view), view);
          XMMATRIX invProj = XMMatrixInverse(&XMMatrixDeterminant(proj), proj);
          XMMATRIX invViewProj = XMMatrixInverse(&XMMatrixDeterminant(viewProj), viewProj);
    
          XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
          XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));
          XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
          XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
          XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
          XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));
          mMainPassCB.EyePosW = mEyePos;
          mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth, (float)mClientHeight);
          mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight);
          mMainPassCB.NearZ = 1.0f;
          mMainPassCB.FarZ = 1000.0f;
          mMainPassCB.TotalTime = gt.TotalTime();
          mMainPassCB.DeltaTime = gt.DeltaTime();
    
          auto currPassCB = mCurrFrameResource->PassCB.get();
          currPassCB->CopyData(0, mMainPassCB);
      }
  • 상수 버퍼뷰 (CBV) 가 서로 다른 빈도로 설정되므로 그 만큼 서술자 테이블도 증가;

7.6 루트 서명 추가 설명

7.6.1 루트 매개변수

  • 루트 매개변수의 종류

    • Descriptor Table(서술자 테이블)

      • 서술자 힙의 시작 위치를 가리키는 테이블

      • CBV, SRV, UAV, Sampler 등 여러 뷰를 묶어서 전달 가능

      • 서술자 힙 안에 있는 뷰들의 묶음을 가리키는 것 (뷰들이 저장된 주소 범위를 가리키는 포인터 역할)

      • 동적으로 리소스를 바꾸기 쉽고, 루트 시그니처 공간도 절약 가능

      • 코드 예시

          CD3DX12_DESCRIPTOR_RANGE cbvTable;
          cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0); // CBV 1개, register(b0)
        
          CD3DX12_ROOT_PARAMETER rootParam;
          rootParam.InitAsDescriptorTable(1, &cbvTable);
      • 구조체

          typedef struct D3D12_ROOT_DESCRIPTOR_TABLE
          {
              UINT NumDescriptorRanges;  // 구간 개수
              const D3D12_DESCRIPTOR_RANGE *pDescriptorRanges; // 구간 배열
          } D3D12_ROOT_DESCRIPTOR_TABLE;
        
          typedef struct D3D12_DESCRIPTOR_RANGE
          {
              D3D12_DESCRIPTOR_RANGE_TYPE RangeType; // 서술자 종류 
              UINT NumDescriptors;  // 이 구간의 서술자 개수
              UINT BaseShaderRegister;  // 묶을 셰이더 인수들의 기준 레지스터 번호 (이 번호부터 차례대로 연결)
              UINT RegisterSpace;  // 셰이더 레지스터들을 지정하는 또 다른 차원
              UINT OffsetInDescriptorsFromTableStart;  // 서술자 테이블 시작에서 이 서술자 구간까지의 오프셋
          } D3D12_DESCRIPTOR_RANGE;
    • Root Descriptor(루트 서술자)

      • CBV / SRV / UAV 1개를 바로 루트에 등록함

      • 디스크립터 테이블 없이, GPU 주소를 바로 루트에 넣는 방식

      • 주의: 루트 시그니처 크기에 영향이 큼
        → 너무 많이 쓰면 성능 저하 가능

      • 코드 예시

          CD3DX12_ROOT_PARAMETER rootParam;
          rootParam.InitAsConstantBufferView(0); // register(b0); Space는 Default 0
      • 구조체

          typedef struct D3D12_ROOT_DESCRIPTOR
          {
              UINT ShaderRegister;  // 서술자와 묶일 셰이더 레지스터
              UINT RegisterSpace; // 셰이더 레지스터들을 지정하는 또 다른 차원
          } D3D12_ROOT_DESCRIPTOR;
    • Root Constant(루트 상수)

      • 쉐이더에 작은 크기의 값을 바로 전달 (최대 128비트, 즉 4개 UINT32 또는 FLOAT)

      • 예시: 정수 인덱스, 타임값, 조명 개수 등

      • 코드 예시

          CD3DX12_ROOT_PARAMETER rootParam;
          rootParam.InitAsConstants(4, 0); // 4개의 32비트 상수, register(b0)
      • 구조체

          typedef struct D3D12_ROOT_CONSTANTS
          {
              UINT ShaderRegister;  // 서술자와 묶일 셰이더 레지스터
              UINT RegisterSpace; // 셰이더 레지스터들을 지정하는 또 다른 차원
              UINT Num32BitValues; // 32비트 상수 개수
          } D3D12_ROOT_CONSTANTS;
  • 성능상의 이유로 1개의 루트 서명에는 최대 64개의 DWORD만 넣을 수 있음

    • 서술자 테이블 : DWORD 1 개
    • 루트 서술자 : DWORD 2 개
    • 루트 상수 : 32비트 상수 당 DWORD 1개
  • ROOT_PARAMETER 구조체

      typedef struct D3D12_ROOT_PARAMETER
      {
          D3D12_ROOT_PARAMETER_TYPE ParameterType;
          union
          {
              D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable;
              D3D12_ROOT_CONSTANTS Constants;
              D3D12_ROOT_DESCRIPTOR Descriptor;
          }
          D3D12_SHADER_VISIBILITY ShaderVisibility;
      } D3D12_ROOT_PARAMETER;

7.6.6 루트 인수의 버전 적용

  • 응용 프로그램이 루트 매개변수에 실제로 전달하는 값을 루트 인수(root argument) 라고 부름
  • 하드웨어가 각 그리기 호출 시점에서의 루트 인수들의 상태('스탭숏')을 자동으로 저장
  • 루트 서명이 셰이더가 실제 사용하는 것보다 많은 필드를 제공할 수 있음
    • 성능을 위해서는 최소한의 루트 서명 크기 유지
    • 루트 매개변수들을 가장 자주 변하는 것에서 덜 변하는 순으로 배치 권장
    • 루트 서명의 전환을 최대한 회피
    • 여러 PSO들이 가능하면 동일한 루트 서명 공유
  • 그리기 호출들 사이에서 루트 인수들을 변경하는 예시
for (size_t i = 0; i < mRitems.size(); ++i)
{
    const auto& ri = mRitems[i];
    ...
    // 현재 프레임 자원과 이 렌더 항목을 위한 CBV의 오프셋
    int cbvOffset = mCurrFrameResourceIndex*(int)mRitems.size();
    cbvOffset += ri.Cbindex
    cbvHandle.Offset(cbvOffset, mCbvSrvDescriptorSize);

    // 이 그리기 호출에 사용할 서술자들을 지정
    cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);

    cmdList->DrawIndexedInstanced(
        ri.IndexCount, 1,
        ri.StartIndexLocation,
        ri.BaseVertexLocation, 0);
}

union

  • 여러 데이터 타입을 한 메모리 공간에 저장 하는 특별한 자료형

  • union 내의 모든 멤버가 동일 메모리 공간 공유

    • 가장 큰 멤버의 크기만큼의 메모리 공간만 할당
  • 메모리 크기가 중요한 곳에서 활용

  • 한 번에 하나의 멤버만 초기화 가능

  • 사용 예시

      #include <iostream>
      using namespace std;
    
      union Data {
          int intValue;
          float floatValue;
          char charValue;
      };
    
      int main() {
          Data data;
    
          // intValue에 값 설정
          data.intValue = 10;
          cout << "intValue: " << data.intValue << endl;
    
          // floatValue에 값 설정 (기존 값 덮어쓰기)
          data.floatValue = 5.5;
          cout << "floatValue: " << data.floatValue << endl;
    
          // charValue에 값 설정
          data.charValue = 'A';
          cout << "charValue: " << data.charValue << endl;
    
          // 동일한 메모리 사용 확인
          cout << "intValue after changing charValue: " << data.intValue << endl;
    
          return 0;
      }

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

STL활용_vector로 set 대체  (0) 2025.04.29
함수 포인터  (0) 2025.04.09
(cpp17)string_view  (2) 2025.04.09
예외처리(simple)  (0) 2025.04.09
Attribute  (0) 2025.03.27

목차

  • [[#고차 함수(Higher-order function)|고차 함수(Higher-order function)]]
  • [[#함수 포인터|함수 포인터]]
  • [[#라이브러리 핸들(dll 함수 호출)|라이브러리 핸들(dll 함수 호출)]]
  • [[#투명 연산자 펑터|투명 연산자 펑터]]
  • [[#::bind()|::bind()]]
  • [[#람다|람다]]
  • [[#std::Invoke|std::Invoke]]
  • [[#트레일링 리턴타입을 쓰는 이유|트레일링 리턴타입을 쓰는 이유]]
    • [[#트레일링 리턴타입을 쓰는 이유#✅ 1. 리턴 타입을 명확히 하고 싶을 때|✅ 1. 리턴 타입을 명확히 하고 싶을 때]]
    • [[#트레일링 리턴타입을 쓰는 이유#✅ 2. 리턴 타입이 **복잡하거나 추론 불가능할 때|✅ 2. 리턴 타입이 **복잡하거나 추론 불가능할 때]]
    • [[#트레일링 리턴타입을 쓰는 이유#✅ 3. 리턴 타입이 auto인 경우라도 const ref 등 복잡한 타입이면|✅ 3. 리턴 타입이 auto인 경우라도 const ref 등 복잡한 타입이면]]
    • [[#트레일링 리턴타입을 쓰는 이유#✅ 정리|✅ 정리]]

고차 함수(Higher-order function)

  • 다른 함수들을 인자로 받거나, 결과값으로 반환하는 함수

    • 함수 포인터, function
  • 현대에서 함수 포인터보다 function 이 많이 사용됨 (편리성, 가독성, 타입 안정성 등)

  • 특징

    • 함수를 인자로 받을 수 있음

        #include <iostream>
        #include <functional>
      
        void applyFunction(std::function<int(int)> func, int value) {
            std::cout << "Result: " << func(value) << std::endl;
        }
      
        int square(int x) {
            return x * x;
        }
      
        int main() {
            applyFunction(square, 5); // square 함수를 전달
            return 0;
        }
    • 함수를 반환할 수 있음

        #include <iostream>
        #include <functional>
      
        std::function<int(int)> multiplier(int factor) {
            return [factor](int x) { return x * factor; };
        }
      
        int main() {
            auto timesTwo = multiplier(2);
            std::cout << "2 x 5 = " << timesTwo(5) << std::endl; // 출력: 2 x 5 = 10
            return 0;
        }

함수 포인터

  • 함수의 주소를 저장하고 참조하는 데 사용되는 포인터

  • 멤버 함수를 전달하기 위해서는 std::bind 를 통해서 객체와 결합된 Wrapper 필요

  • 사용 예시

     #include <iostream>
     using namespace std;
    
     // 함수 정의
     int add(int a, int b) { return a + b; }
    
     int main() {
         // 함수 포인터 선언
         int (*funcPtr)(int, int) = add;
    
         // 함수 포인터 호출
         cout << "Result: " << funcPtr(5, 3) << endl; // 출력: Result: 8
         return 0;
     }
  • 함수 테이블로 사용 예시

      int add(int a, int b) { return a + b; }
      int subtract(int a, int b) { return a - b; }
    
      int main() {
          // 함수 포인터 배열
          int (*operations[2])(int, int) = {add, subtract};
    
          cout << "Add: " << operations[0](5, 3) << endl; // 출력: Add: 8
          cout << "Subtract: " << operations[1](5, 3) << endl; // 출력: Subtract: 2
          return 0;
      }
  • Wrapper 이용 멤버 함수 전달

      #include <iostream>
      #include <functional>
    
      class MyClass {
      public:
          void memberCallback(int value) {
              std::cout << "Member function called with value: " << value << std::endl;
          }
      };
    
      void executeCallback(const std::function<void(int)>& callback, int arg) {
          callback(arg);
      }
    
      int main() {
          MyClass obj;
    
          // std::function을 사용하여 멤버 함수 래핑
          std::function<void(int)> callback = std::bind(&MyClass::memberCallback, &obj);
    
          // 콜백 함수 실행
          executeCallback(callback, 42);
    
          return 0;
      }

라이브러리 핸들(dll 함수 호출)

  • HMODULE lib { ::LoadLibrary("hardware.dll") };
    • 이렇게 호출해서 리턴된 결과를 라이브러리 핸들이라 함
    • 호출 과정에서 error발생 시, NULL 리턴
  • 타입 앨리어스를 이용해서 프로토타입을 가진 함수에 대한 포인터 이름 정의 가능
    • using ConnectFunction = int(__stdcall*)(bool, int, const char*);
  • DLL에 있는 함수 포인터 구하기
    • ConnectFunction connect { (ConnectFunction)::GetProcAddress(lib, "Connect");}

투명 연산자 펑터

  • 비투명 연산자 펑터보다 성능이 우수 (가능하면 투명 연산자 펑터 사용)
    • 타입을 추론하여 읽기 떄문에, 형변환이 발생x
  • multiplies<int> -> multiplies<> 로 적어도 됨
  • 이종 룩업 : C14에 도입;
    • 연관 컨테이너에서 키 타입과 다른 타입을 사용하여 검색 수행할 수 있도록 지원
set<string, less<>> mySet;
auto i1 { mySet.find("Key") };     // 스트링 생기지도 않고 메모리 할당x
auto i2 { mySet.find("Key"sv) };   // 스트링 생기지도 않고 메모리 할당x

::bind()

  • <functional> 의 함수
  • 함수 포인터가 멤버 변수를 전달할 떄, 객체와 wrapping 하기위해 많이 사용
  • bind() 의 매개변수는 기본적으로 복제본을 생성
    • bind(func, ref(index)) 를 하면 참조로 넘김

람다

  • 람다 선언자(lambda introducer), 캡처 블록 : []

    • 변수를 캡처해서 람다 표현식 본문에 쓸 떄는, 해당 대괄호 안에 넣기
  • 기본 사용 예제

      auto basicLambda { []{ cout << "Hello from Lambda" << endl; } };
      basicLambda();
  • 람다 클로저 : 컴파일러는 모든 람다 표현식을 자동으로 함수 객체로 변환

  • 트레일링 리턴타입 : ->

  • 람다의 () 오퍼레이터 호출은 기본적으로 const, 따라서 멤버변수(캡쳐변수)의 값 변경이 불가

    • mutable 변수를 캡처변수로 사용 시에는 변경 가능
    • 캡처변수에 &를 붙이면 레퍼런스로 캡처 시, 변경 가능
  • 캡처 디폴트 : 람다 표현식이 속한 상위 스코프의 변수를 모두 캡처하는 방법

    • [=] : 스코프에 있는 변수를 모두 값으로 캡처
    • [&] : 스코프에 있는 변수를 모두 레퍼런스로 캡처
      • [=, &x, &y] , [&, x] 등 여러 조합으로 가능
      • [*this] : 현재 객체의 복제본을 캡처 (실행 시점에 객체가 살아 있지 않을 떄, 유용)
    • [p = move(myPtr)]{} 와 같이 std::move 를 비롯한 모든 종류의 표현식으로 초기화 가능
  • 람다 표현식을 함수의 return 타입으로 반환 가능

    • C14 에서는 auto 함수 리턴 추론을 통해 쉽게 사용가능

std::Invoke

  • 모든 콜러블 객체에 대해 일련의 매개변수를 지정해서 호출 가능
  • invoke({콜러블객체}, {매개변수})

트레일링 리턴타입을 쓰는 이유

✅ 1. 리턴 타입을 명확히 하고 싶을 때

컴파일러가 리턴 타입을 제대로 추론하지 못하거나, 오버로드, 템플릿 등의 상황에서 헷갈릴 수 있을 때 명시적으로 써주는 게 좋아요.

auto func = [](bool b) -> int {
    if (b) return 1;
    else return 2;
};

이 경우는 사실 없어도 되지만, 복잡한 상황에선 명시가 도움이 됩니다.


✅ 2. 리턴 타입이 **복잡하거나 추론 불가능할 때

특히 auto 키워드가 리턴 타입에 들어가는 경우나, 템플릿 코드에서는 반드시 써야 할 때도 있어요.

예:

template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

여기선 a + b의 타입을 컴파일러가 추론하도록 하면서도, 템플릿 밖에서 리턴 타입을 지정해야 하기 때문에 -> decltype(...)이 필요합니다.

람다에서도 똑같아요:

auto l = [](auto a, auto b) -> decltype(a + b) {
    return a + b;
};

✅ 3. 리턴 타입이 auto인 경우라도 const ref 등 복잡한 타입이면

예:

auto l = [](const std::string& s) -> const std::string& {
    return s;
};

이런 식으로 참조 타입, const, 포인터 등을 정확히 명시할 때 ->는 필요합니다.


✅ 정리

상황 -> 필요 여부
간단한 리턴 (int, double 등) ❌ 없어도 됨
리턴 타입 추론 어려움 ✅ 필요함
템플릿, auto, decltype 등 복잡한 타입 ✅ 필요함
const, 참조, 포인터 리턴 등 ✅ 필요함

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

STL활용_vector로 set 대체  (0) 2025.04.29
union  (0) 2025.04.10
(cpp17)string_view  (2) 2025.04.09
예외처리(simple)  (0) 2025.04.09
Attribute  (0) 2025.03.27

+ Recent posts

let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });