콘텐츠로 이동

C++ 함수 매개변수·반환 규칙

C++ 목차

상태: 채택 · 적용 범위: 일반 함수의 입력·입출력·결과 전달 · 예제: C++11 이상, structured binding·[[nodiscard]]는 C++17 이상 · 개정일: 2026-10-16

입력·변경·결과를 호출부에 드러낸다. 스마트 포인터는 소유권 전달 규칙을 우선한다.

기본 선택

목적 기본 방식 강도
복사 비용이 작은 입력 T 값 전달 권장
복사 비용이 큰 객체를 읽기만 함 const T& 권장
기존 객체를 읽고 수정 T& 권장
부재가 유효한 비소유 입력 const T* 또는 T* 권장
새 결과 하나 생성 T 값 반환 권장
의미상 묶인 결과 여러 개 이름 있는 구조체 반환 권장
호출자 버퍼 재사용·외부 API 계약을 명시한 출력 인자 허용 선택

입력과 입출력

전달 방식은 비용·의도로 판단하며 바이트 크기만으로 결정하지 않는다.

#include <string>

bool HasPrefix(
    const std::string& text,
    char prefix)
{
    return !text.empty() && text.front() == prefix;
}

void AddBonus(
    int& damage,
    int bonusDamage)
{
    damage += bonusDamage;
}

T&는 기존 객체 수정이지 출력 전용이 아니다. 포인터의 null 의미와 수명 계약을 명시한다.

필수: 입력 참조를 호출 뒤에 저장하려면 수명 계약을 둔다. 복사·이동 가능한 값을 항상 보관한다면 값 전달 후 이동 저장을 기본안으로 사용할 수 있다.

#include <string>
#include <utility>

class Player
{
public:
    void SetName(std::string name)
    {
        mName = std::move(name);
    }

private:
    std::string mName;
};

조건부 저장, 버퍼 재사용, 비용이 큰 타입은 실제 호출 패턴에 따라 별도로 검토한다. 단순 입력에 관성적으로 T&&를 사용하지 않는다. 전달 템플릿의 T&&·std::forward는 별도 주제로 둔다.

반환값으로 결과를 표현한다

큰 객체도 값 반환을 우선하며 비용은 복사 생략·이동·호출 패턴으로 확인한다.

#include <vector>

std::vector<int> CreateScores(int count)
{
    std::vector<int> scores(count, 0);
    return scores;
}

이 예제는 count >= 0을 전제로 한다. 일반 지역 결과는 return result;로 반환하고 return std::move(result);를 기계적으로 추가하지 않는다. 값 반환 타입에 최상위 const를 붙이지 않는다. 참조로 반환하는 const T&와는 다른 규칙이다.

struct Size
{
    int width;
    int height;
};

Size GetDefaultSize()
{
    return {1280, 720};
}

구조체와 pair·tuple의 선택

상태: 채택 · 적용 범위: 복합 결과의 반환형 선택 · 개정일: 2026-10-09

요소의 크기·개수나 함수 길이가 아닌 의미·순서로 선택한다.

반환 방식 사용하는 경우 강도·예외
이름 있는 구조체 결과들이 하나의 개념을 이루거나 필드 이름으로 의미를 설명해야 하는 경우 권장. 크기·범위·파싱 결과 등
std::pair 두 독립적인 값을 간단히 묶으며 각 값의 의미와 순서가 명확한 경우 선택. 간단한 임시 결과·범용 조합
std::tuple 여러 독립적인 값을 임시로 묶거나 범용·가변 템플릿에서 결과를 조합하는 경우 선택. 별도 개념을 나타내는 결과는 구조체 우선
표준·외부 API의 반환형 해당 API가 반환형을 지정한 경우 해당 계약 우선. 이름만 바꾸기 위한 래퍼는 필수가 아님

width·height처럼 하나의 개념인 결과는 위 Size로 표현한다. tuple 별칭만으로는 필드 의미가 드러나지 않는다.

tuple은 C++11, 구조체에도 가능한 structured binding은 C++17부터다.

const auto size = GetDefaultSize();
const int width = size.width;

// C++17: Size 구조체도 분해할 수 있다.
const auto [defaultWidth, defaultHeight] = GetDefaultSize();

structured binding은 필드 순서에 의존한다. 지역 이름이 반환 계약을 대신하지 않으므로 이름이 필요한 곳은 필드 접근을 유지한다. 성공·오류 계약은 결과 없음과 실패를 따른다.

구조체·tuple의 성능 우위를 가정하지 않고 타입·복사/이동·컴파일러·호출 패턴으로 검증한다.

출력 인자를 허용하는 경우

외부 API의 지정된 시그니처, 호출자가 마련한 버퍼의 재사용, 측정으로 확인된 비용 절감이 필요한 경우 출력 인자를 허용한다. 새 객체 하나를 반환할 수 있는 일반 함수의 기본형으로 삼지는 않는다.

필수: 입력과 출력이 섞이면 무엇을 읽고 무엇을 쓰는지 설명한다. 실패 시 출력 유지·초기화·부분 변경 중 어떤 동작인지 명시하고 구현과 일치시킨다.

#include <string>

/// @brief 경로에 해당하는 이름을 읽어 output에 기록한다.
/// @return 성공 시 true. 실패 시 false이며 output은 변경하지 않는다.
bool TryReadName(
    const std::string& path,
    std::string& output);

출력은 입력 뒤에 두는 것을 권장하며 외부 API 순서는 유지한다. 예시의 실패 계약은 실제 구현과 일치시켜야 한다.

결과 없음과 실패

  • 반환할 데이터가 없는 명령은 void, 참·거짓 질의나 성공 여부만 필요한 연산은 bool을 사용할 수 있다.
  • 데이터가 없을 수 있는 결과에는 C++17 이상에서 std::optional<T>를 고려한다. 오류 원인까지 담는 형식은 아니다.
  • 오류 원인이 필요한 연산은 결과 타입·오류 코드·예외 정책을 별도로 정한다. std::expected를 사용한다면 C++23 및 도구 지원을 확인한다.
  • 0, -1, 빈 문자열을 보편적인 실패값으로 삼지 않는다. 정상 결과와 구분되는지 먼저 확인한다.

이 절은 반환형 선택의 경계만 다룬다. 프로젝트 전체의 예외 허용 범위나 오류 처리 방식은 아직 확정하지 않는다.

반환값 무시와 nodiscard

권장: 결과 폐기가 잘못된 사용·중요한 누락인 API에 [[nodiscard]]를 붙인다(C++17). bool·Getter라는 이유로 일괄 적용하지 않는다.

실패 후 진행·종료·소비 데이터·계산값 등 후속 동작의 전제인지 판단한다. 내부에서 실패를 기록해도 호출자가 중단 여부를 정해야 하면 확인한다.

변수에 저장하는 것만으로 진단을 피할 수 있어 실제 검사를 보장하지 않는다. 적용 대상·버전은 attribute 초안을 참조한다.

의도적인 반환값 무시

상태: 채택 · 적용 범위: 결과를 사용하지 않아도 계약이 성립하는 호출부 · 개정일: 2026-10-09

  • 필수: 우선 결과를 검사·사용·상위 호출자에게 전달해야 하는지 확인한다. 실패 후 진행 여부를 정해야 하는 호출에 경고 제거만을 위한 캐스트를 넣지 않는다.
  • 권장: 반환값을 의도적으로 무시함을 표시할 때는 (void)Function();을 기본 표기로 사용한다. 결과를 버리는 이유가 검토상 중요한 호출에도 이 표기를 사용한다.
  • 선택: 결과 무시가 자연스럽고 계약상 문제가 없는 일반 호출은 Function();만 작성할 수 있다. 모든 비-void 호출에 별도 표기를 요구하지 않는다.
  • 필수: [[nodiscard]] 결과를 무시하는 허용된 예외는 명시적으로 표시한다. 결과 무시가 허용되는지를 먼저 판단하며, attribute가 없는 함수도 결과 확인이 필요한 계약이면 검사한다.
  • 필수: 실패 결과를 버리는 예외에는 실패를 어디서 보고하는지와 이후 어떻게 진행하는지 남긴다. 주변 계약에서 분명하면 같은 설명을 호출마다 반복할 필요는 없다.
  • 선택: 프로젝트의 기존 스타일이나 도구 정책에 따라 static_cast<void>(Function());·std::ignore = Function();도 사용할 수 있다. std::ignore는 <tuple>을 직접 포함하고 지원 라이브러리·분석 도구의 동작을 확인한다. 같은 프로젝트에서는 특별한 이유 없이 여러 표기를 섞지 않는 것을 권장한다. (void)를 수치·포인터의 C 스타일 캐스트 규칙과 구분한다.
// 종료 정리에서 완료를 시도한다. 실패는 함수 내부에서 보고하고 정리를 계속한다.
// 이 경로의 결과 폐기는 GPU 완료를 보장하지 않는다.
(void)WaitForGpu();

소멸자도 실패 무시의 안전성을 보장하지 않는다. 정리 계약을 확인하고 정상 실행·재사용이 완료 여부에 의존하면 결과를 검사한다.

void 캐스트는 표준의 진단 권고 예외이며 호출·부작용·예외는 유지된다. 미사용 변수·[[maybe_unused]]로 결과를 숨기는 방식을 기본으로 삼지 않는다.

확인 사례·기존 표기·추가 <tuple> 의존성을 고려해 (void)를 기본으로 선택했다. C4834의 std::ignore 권고와는 다르며 대체 표기는 허용한다. C26457 등 추가 진단은 별도 확인하고 설정은 변경하지 않는다.

관련 규칙과 예외

  • 함수명은 명명 규칙, 여러 줄 매개변수와 닫는 괄호 배치는 포맷 규칙을 따른다.
  • 매개변수의 const 위치와 지역 변수 적용 기준은 const 사용 규칙에 둔다.
  • 포인터·참조·뷰 반환의 유효 기간과 비동기 저장은 소유권·수명 규칙에 둔다.
  • 매개변수가 많거나 비슷한 bool·정수 인자가 반복되면 이름 있는 옵션 타입이나 열거형을 검토한다. 단순히 인자 개수를 줄이기 위한 무관한 값의 묶음은 피한다.
  • 오버라이드·콜백·C ABI 등 외부에서 지정한 함수 형태는 해당 계약을 우선한다.

관련 문서

근거와 출처

기존 자료(로컬 참고 자료)의 입력·반환·COM 출력을 참고하되 비용·의도로 구분하고 외부 API 출력을 일반 함수 기본으로 확대하지 않는다.

C++ Core Guidelines의 입력·입출력 구분과 값·구조체 반환 권고를 참고했다. 값 전달 후 저장, 출력 인자 배치와 실패 계약의 구체화는 이 저장소의 선택이다.

출처 확인일: 2026-09-12.

추가 확인일: 2026-10-09. 사용자 선택으로 pair·tuple 범위와 의도적 결과 무시를 구체화했다. 기존 자료의 예외를 공통 확대하지 않으며 구조체·nodiscard 권장은 유지한다.

  • F.21 추가 비교: 2026-06-14 표기본, C++20 중심. 구조체 우선·독립 값·가변 템플릿의 tuple 사용을 참고하고 간단한 임시 결과 허용은 개인 선택으로 정했다.
  • C++ 작업 초안 — dcl.attr.nodiscard: 경고 권고와 명시적인 void 변환 예외. 갱신되는 작업 초안이며 적용 버전은 C++17 기본형·C++20 이유 문자열로 구분한다.
  • Microsoft C4834 — Remarks·Example: Visual Studio 문서 뷰 msvc-170. 경고·의도적 결과 무시와 std::ignore 권고, C26457 가능성을 확인했다.

채택일: 2026-09-12. 명시된 적용 범위 안에서 채택하며, 후속 정리 대상은 제외한다.

미결정 사항과 변경 기록

  • 2026-10-16: 반복 설명·중복 예시·배경을 줄여 핵심 규칙과 예시 중심으로 정리했다. 규칙 강도·예외·버전과 채택·초안·미결정 상태는 유지했다.

프로젝트 오류 처리와 진단 설정은 별도 결정하며 보류·폐기로 확정한 항목은 없다.

  • 2026-10-09: 사용자 선택으로 구조체·pair·tuple의 선택 기준과 의도적 결과 무시의 (void) 권장 기본·표기 생략·대체 표기 허용 범위를 채택했다. 기존 nodiscard 기준과 진단의 한계를 설명하고 attribute 초안으로 연결했다. 오류 처리·진단 설정은 프로젝트별 결정으로 유지한다.