C++ const 에러 | "passing as const" 컴파일 에러 완벽 해결
이 글의 핵심
C++ const 에러에 대한 실전 가이드입니다.
들어가며: “passing as const 에러가 계속 나요"
"const를 어디에 붙여야 하는지 모르겠어요”
C++에서 const는 타입 안전성을 높이는 핵심 키워드이지만, const 관련 에러는 초보자가 가장 자주 겪는 컴파일 에러 중 하나입니다.
// ❌ 에러 코드
void print(std::string& s) { // 비const 참조
std::cout << s << '\n';
}
int main() {
print("Hello"); // 임시 객체 → const 참조만 가능
}
// error: cannot bind non-const lvalue reference of type 'std::string&'
// to an rvalue of type 'std::string'
이 글에서 다루는 것:
- const 관련 에러 10가지 패턴
- const 참조 vs 비const 참조
- const 멤버 함수
- mutable 키워드
- const_cast 사용법과 주의사항
목차
- 자주 나오는 const 에러 10가지
- const 참조 vs 비const 참조
- const 멤버 함수
- const 포인터 4가지 패턴
- mutable 키워드
- const_cast 주의사항
- 정리
1. 자주 나오는 const 에러 10가지
에러 1: passing as const (가장 흔함)
// ❌ 에러 코드
void modify(std::string& s) { // 비const 참조
s += " world";
}
int main() {
modify("Hello"); // 임시 객체는 비const 참조에 바인딩 불가
}
// error: cannot bind non-const lvalue reference of type 'std::string&'
// to an rvalue of type 'std::string'
해결:
// ✅ const 참조로 변경
void print(const std::string& s) { // const 참조
std::cout << s << '\n';
}
int main() {
print("Hello"); // OK
}
에러 2: discards qualifiers
// ❌ 에러 코드
class MyClass {
int value_;
public:
int getValue() { // const 없음
return value_;
}
};
void print(const MyClass& obj) {
std::cout << obj.getValue() << '\n'; // const 객체에서 비const 함수 호출
}
// error: passing 'const MyClass' as 'this' argument discards qualifiers
해결:
// ✅ const 멤버 함수
class MyClass {
int value_;
public:
int getValue() const { // const 추가
return value_;
}
};
에러 3: assignment of read-only variable
// ❌ 에러 코드
void foo() {
const int x = 42;
x = 99; // const 변수 수정
}
// error: assignment of read-only variable 'x'
해결: const를 제거하거나, 새 변수 사용.
// ✅ const 제거
void foo() {
int x = 42;
x = 99; // OK
}
에러 4: cannot convert const to non-const
// ❌ 에러 코드
void modify(int* ptr) { // 비const 포인터
*ptr = 99;
}
int main() {
const int x = 42;
modify(&x); // const int* → int* 변환 불가
}
// error: invalid conversion from 'const int*' to 'int*'
해결:
// ✅ const 포인터로 변경
void print(const int* ptr) { // const 포인터
std::cout << *ptr << '\n';
}
int main() {
const int x = 42;
print(&x); // OK
}
에러 5: const 멤버 함수에서 멤버 수정
// ❌ 에러 코드
class Counter {
int count_;
public:
void increment() const { // const 멤버 함수
++count_; // const 함수에서 멤버 수정 불가
}
};
// error: increment of member 'Counter::count_' in read-only object
해결:
// ✅ 해결 1: const 제거
void increment() { // 비const 함수
++count_;
}
// ✅ 해결 2: mutable 사용 (논리적 const)
class Counter {
mutable int count_; // mutable: const 함수에서도 수정 가능
public:
void increment() const {
++count_; // OK
}
};
에러 6: 반환 타입 const 불일치
// ❌ 에러 코드
class MyClass {
std::string name_;
public:
std::string& getName() const { // const 함수가 비const 참조 반환
return name_; // const 멤버를 비const 참조로 반환 불가
}
};
// error: binding reference of type 'std::string&' to 'const std::string'
// discards qualifiers
해결:
// ✅ const 참조 반환
class MyClass {
std::string name_;
public:
const std::string& getName() const { // const 참조 반환
return name_;
}
};
에러 7: const 객체에서 비const 반복자
// ❌ 에러 코드
void print(const std::vector<int>& vec) {
for (auto it = vec.begin(); it != vec.end(); ++it) { // begin()은 const_iterator 반환
std::cout << *it << '\n';
}
}
// error: conversion from 'const_iterator' to 'iterator'
해결:
// ✅ const_iterator 사용
void print(const std::vector<int>& vec) {
for (auto it = vec.cbegin(); it != vec.cend(); ++it) { // cbegin/cend
std::cout << *it << '\n';
}
}
// ✅ 또는 범위 기반 for
void print(const std::vector<int>& vec) {
for (int x : vec) {
std::cout << x << '\n';
}
}
에러 8: const 오버로드 불일치
// ❌ 에러 코드
class MyClass {
std::vector<int> data_;
public:
int& operator { // 비const 버전만
return data_[idx];
}
};
void print(const MyClass& obj) {
std::cout << obj[0] << '\n'; // const 객체에서 비const operator[] 호출
}
// error: passing 'const MyClass' as 'this' argument discards qualifiers
해결:
// ✅ const 오버로드 추가
class MyClass {
std::vector<int> data_;
public:
int& operator { // 비const 버전
return data_[idx];
}
const int& operator const { // const 버전
return data_[idx];
}
};
에러 9: const 반복자 수정
// ❌ 에러 코드
void modify(const std::vector<int>& vec) {
for (auto it = vec.begin(); it != vec.end(); ++it) {
*it = 99; // const_iterator는 수정 불가
}
}
// error: assignment of read-only location
해결: 매개변수를 비const로 변경.
// ✅ 비const 참조
void modify(std::vector<int>& vec) { // const 제거
for (auto& x : vec) {
x = 99; // OK
}
}
에러 10: const 멤버 초기화 실수
// ❌ 에러 코드
class MyClass {
const int value_;
public:
MyClass() {
value_ = 42; // const 멤버는 대입 불가
}
};
// error: assignment of read-only member 'MyClass::value_'
해결:
// ✅ 초기화 리스트 사용
class MyClass {
const int value_;
public:
MyClass() : value_(42) { // 초기화 리스트
// ...
}
};
2. const 참조 vs 비const 참조
언제 const 참조를 쓰는가?
// ✅ 읽기만 하는 함수 → const 참조
void print(const std::string& s) {
std::cout << s << '\n';
}
// ✅ 수정하는 함수 → 비const 참조
void append(std::string& s, const std::string& suffix) {
s += suffix;
}
// ✅ 작은 타입 → 값 전달
void add(int a, int b) { // int는 8바이트 이하 → 값 전달
return a + b;
}
const 참조의 장점
- 임시 객체 바인딩 가능
void print(const std::string& s) {
std::cout << s << '\n';
}
print("Hello"); // ✅ 임시 객체 OK
print(std::string("World")); // ✅ OK
- 복사 방지
// ❌ 복사 (느림)
void process(std::vector<int> vec) { // 값 전달 → 복사
// ...
}
// ✅ 참조 (빠름)
void process(const std::vector<int>& vec) { // 참조 → 복사 없음
// ...
}
3. const 멤버 함수
const 멤버 함수 규칙
class MyClass {
int value_;
public:
// ✅ const 멤버 함수: 멤버 변수 읽기만
int getValue() const {
return value_; // OK
}
// ❌ const 멤버 함수에서 수정
void setValue(int v) const {
value_ = v; // 컴파일 에러
}
};
const 오버로드
class MyClass {
std::vector<int> data_;
public:
// 비const 버전: 수정 가능한 참조 반환
int& operator {
return data_[idx];
}
// const 버전: 읽기 전용 참조 반환
const int& operator const {
return data_[idx];
}
};
// 사용
MyClass obj;
obj[0] = 42; // 비const 버전 호출
const MyClass& cobj = obj;
int x = cobj[0]; // const 버전 호출
// cobj[0] = 99; // 컴파일 에러
4. const 포인터 4가지 패턴
패턴 1: 포인터 to const (가리키는 값 const)
const int* ptr = &x;
// *ptr = 42; // ❌ 값 수정 불가
ptr = &y; // ✅ 포인터 재할당 가능
패턴 2: const 포인터 (포인터 자체 const)
int* const ptr = &x;
*ptr = 42; // ✅ 값 수정 가능
// ptr = &y; // ❌ 포인터 재할당 불가
패턴 3: const 포인터 to const (둘 다 const)
const int* const ptr = &x;
// *ptr = 42; // ❌ 값 수정 불가
// ptr = &y; // ❌ 포인터 재할당 불가
패턴 4: 일반 포인터
int* ptr = &x;
*ptr = 42; // ✅ 값 수정 가능
ptr = &y; // ✅ 포인터 재할당 가능
읽는 법
오른쪽에서 왼쪽으로 읽기:
const int* ptr;
// ptr is a pointer to const int
// "const int를 가리키는 포인터"
int* const ptr;
// ptr is a const pointer to int
// "int를 가리키는 const 포인터"
const int* const ptr;
// ptr is a const pointer to const int
// "const int를 가리키는 const 포인터"
5. mutable 키워드
사용 시나리오
mutable은 논리적으로 const이지만 물리적으로 변경이 필요한 멤버에 사용합니다.
class Cache {
mutable std::unordered_map<Key, Value> cache_; // mutable
mutable std::mutex mutex_; // mutable
public:
Value get(const Key& key) const { // const 함수
std::lock_guard lock(mutex_); // mutex 잠금 (수정)
auto it = cache_.find(key);
if (it != cache_.end()) {
return it->second; // 캐시 히트
}
Value value = loadFromDB(key);
cache_[key] = value; // 캐시 갱신 (수정)
return value;
}
};
사용 예:
- 캐시: 논리적으로 const (외부에서 보면 변경 없음)
- 뮤텍스: 잠금은 논리적 상태 변경 아님
- 참조 카운트: 내부 구현 디테일
6. const_cast 주의사항
const_cast란?
const_cast는 const를 강제로 제거하는 캐스팅입니다.
void legacyFunc(char* str); // C API (const 없음)
void wrapper(const char* str) {
legacyFunc(const_cast<char*>(str)); // const 제거
}
위험성
// ❌ 미정의 동작
const int x = 42;
int* ptr = const_cast<int*>(&x);
*ptr = 99; // UB! (원래 const인 객체 수정)
std::cout << x << '\n'; // 42? 99? 예측 불가
규칙: 원래 const가 아닌 객체만 const_cast로 수정 가능.
// ✅ 안전한 const_cast
int x = 42; // 원래 비const
const int* cptr = &x;
int* ptr = const_cast<int*>(cptr); // const 제거
*ptr = 99; // OK (원래 비const였으므로)
실전 사례 분석
사례 1: STL 알고리즘 const 에러
에러 코드:
// ❌ 에러 코드
void sortData(const std::vector<int>& data) {
std::sort(data.begin(), data.end()); // const vector는 정렬 불가
}
// error: no matching function for call to 'std::sort'
해결:
// ✅ 비const 참조
void sortData(std::vector<int>& data) { // const 제거
std::sort(data.begin(), data.end());
}
// ✅ 또는 복사본 정렬
std::vector<int> sortData(const std::vector<int>& data) {
std::vector<int> sorted = data; // 복사
std::sort(sorted.begin(), sorted.end());
return sorted;
}
사례 2: getter/setter const 불일치
에러 코드:
// ❌ 에러 코드
class Person {
std::string name_;
public:
std::string& getName() { // 비const 함수
return name_;
}
};
void print(const Person& p) {
std::cout << p.getName() << '\n'; // const 객체에서 비const 함수 호출
}
// error: passing 'const Person' as 'this' argument discards qualifiers
해결:
// ✅ const 오버로드
class Person {
std::string name_;
public:
std::string& getName() { // 비const 버전
return name_;
}
const std::string& getName() const { // const 버전
return name_;
}
};
const correctness 체크리스트
함수 매개변수
- 읽기만 하는 매개변수는 const 참조인가?
- 작은 타입 (int, double)은 값 전달인가?
- 수정하는 매개변수는 비const 참조인가?
멤버 함수
- 멤버 변수를 수정하지 않는 함수는 const인가?
- getter는 const 멤버 함수인가?
- const 오버로드가 필요한가? (operator[], at 등)
반환 타입
- const 멤버 함수는 const 참조를 반환하는가?
- 임시 객체를 반환하는가? (const 참조 금지)
정리
const 에러 해결 체크리스트
| 에러 메시지 | 원인 | 해결법 |
|---|---|---|
passing as const | 임시 객체를 비const 참조에 전달 | const 참조로 변경 |
discards qualifiers | const 객체에서 비const 함수 호출 | 함수를 const로 |
assignment of read-only | const 변수 수정 | const 제거 또는 새 변수 |
cannot convert const to non-const | const 포인터를 비const로 전달 | 매개변수를 const로 |
const 사용 규칙
- 읽기만 하는 매개변수는 const 참조
- 멤버 변수를 수정하지 않는 함수는 const
- const 오버로드 제공 (operator[], at 등)
- mutable은 논리적 const에만
- const_cast는 최후 수단 (레거시 API 연동)
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ const 기초 | const 변수·참조·포인터 완벽 가이드
- C++ const 멤버 함수 | const correctness 가이드
- C++ mutable 키워드 | 논리적 const 구현
- C++ 타입 안전성 | const·constexpr·static_assert
마치며
const는 C++의 타입 안전성을 높이는 핵심 키워드입니다. const 에러는 처음에는 번거롭지만, 버그를 컴파일 타임에 잡아주는 강력한 도구입니다.
핵심 원칙:
- 읽기만 하는 매개변수는 const 참조
- 멤버 변수를 수정하지 않는 함수는 const
- const 오버로드 제공
- const_cast는 최후 수단
const correctness를 지키면 버그를 줄이고, 코드 의도를 명확히 하며, 컴파일러 최적화에도 도움이 됩니다.
다음 단계: const를 이해했다면, C++ constexpr 가이드에서 컴파일 타임 상수를 배워보세요.
관련 글
- C++ 시리즈 전체 보기
- C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
- C++ ADL |
- C++ Aggregate Initialization |