C++에서 상수 문자열을 참조자가 참조할 수 있게 하는 방법

익명 사용자의 이미지

원래 참조자가 참조할 수 있는 대상은 변수로 한정되긴 하나 const를 붙여줌으로써 상수를 참조 대상으로 확장시킬 수 있잖아요?

그래서 문자열은 첫번째 문자값에 대한 주소가 반환되니까

const char * (&ref_str) = "Hello reference";

//이렇게 선언하니까 컴파일 에러가 발생하더군요. 왜 이 선언은 사용할 수 없는 형태인지 모르겠습니다.

두번째로 생각해낸 것이

const char (&ref_str)[] = "Hello refernece";

//두 번째 형태는 컴파일 에러 없이 출력도 잘 됩니다.

제가 생각하기에는 두 개의 선언방식 모두 동일한 의미를 뜻하니까 무엇을 사용하든 무관할 것 같은데..무엇이 문제인지 알고 싶네요.

 의 이미지

// const char * (&ref_str1) = "Hello reference";
// error: invalid initialization of non-const reference of type ‘const char*&’ from an rvalue of type ‘const char*’
// const char (&ref_str2)[] = "Hello refernece";
// error: invalid initialization of reference of type ‘const char (&)[]’ from expression of type ‘const char [16]’
#if __cplusplus >= 201103L
const char * (&&ref_str3) = "Hello reference"; // written by iluvtrollhd
#endif
const char (&ref_str4)[16] = "Hello reference";
const char * const (&ref_str5) = "Hello reference";

1. "Hello reference"의 type은 "array of 16 const char" 입니다. NUL문자는 빠트리기 쉽지요.

2. string literal을 곧바로 reference에 묶어 버리면 Array-to-pointer conversion을 피할 수 있습니다. 그러면 C++11의 rvalue reference 없이도 레퍼런스에 담을 수 있겠지요. (ref_str4 참조)

3. C++03에는 rvalue reference가 없지만, 때로는 reference가 오른값에 묶일 수 있습니다. 바로 많이들 사용하셨을 상수 참조자 용법입니다. C++11 이후로 이런 변칙적인 문법은 서서히 잊혀져 갈지도 모르겠네요. (ref_str5 참조)

4. 제 컴파일러로는 왜 ref_str2 선언이 컴파일이 안 될까요. 질문자님은 된다고 하셨는데 말이죠. 저는 g++ 4.8.5를 썼고 -std=c++03 -pedantic -pedantic-errors 옵션을 주었습니다.

 의 이미지

흠.

찾아보니 g++ 4.9.2까지는 컴파일이 안 되고 g++ 4.9.3부터 컴파일이 되는군요.
옵션은 모두 -std=c++03 -pedantic -pedantic-errors

익명 사용자의 이미지

3. 제가 lvalue와 rvalue의 개념을 혼동해서인지 이해가 안되는 부분이 있습니다.

const char *(&ref_str) -> lvalue reference까지 이해가 됩니다.

lvalue reference는 lvalue에만 한정되어 참조자 역할을 한다는 것이 결국 저는 이렇게 받아들여지네요.

원래 참조자라는 것이 lvalue 값에만 한정되어 사용되는 것이 원칙입니다.

제가 언급한 예시처럼 가장 흔한 rvalue로 상수를 말할 수 있겠네요. 상수를 참조해버리면 참조자를 통해 접근뿐만 아니라 값의 변경도 가능해야 하는데 rvalue는 해당 연산식(?)이 끝나버리면 재참조할 수 있는 값의 속성이 아니기 때문이죠..

그래서 이걸 야매..라고 해야할지는 모르겠지만 억지로 방법을 생각해냅니다.
const 키워드를 사용하는거죠. const 키워드를 사용함으로써 참조자에게 속성을 붙여줍니다.
참조자가 참조하는 대상이 상수이므로 참조자를 통한 값 변경을 금해버리는 것이죠.

그래서 const int &ref_integer = 100; //error 발생하지 않음

만일 제가 char * (&ref_str) 이렇게 선언했다면 답변해주신분의 말씀이 맞습니다. 하지만 그러한 에러 발생을 피하기 위해 const char *(&ref_str)을 선언한 것이고요.

즉 const 키워드가 lvalue reference의 값에 rvalue가 들어갈 수 있게 해주는 것이죠.

 의 이미지

제가 질문의 요점을 잘못 잡았군요.

Quote:
원래 참조자가 참조할 수 있는 대상은 변수로 한정되긴 하나 const를 붙여줌으로써 상수를 참조 대상으로 확장시킬 수 있잖아요?

C++03에서 일반적으로 참조자는 lvalue에 묶입니다. 사실 그냥 "변수로 한정"된다고만 말하기에는 조금 미묘합니다:

int global;
 
int &return_a_lvalue(void){
    return global;
}
 
class widget{
public:
    operator int &(void){
        return global;
    }
};
 
void example1(void){
    int local;
    int &ref1 = local; // local은 int타입 lvalue. 변수 lvalue를 가리킴.
 
    // int &ref2 = 10; // Error: 10은 int타입 rvalue
 
    int &ref3 = return_a_lvalue(); // 이 함수는 int타입 lvalue를 반환함. 변수 global을 가리킴.
 
    int integer_array[10];
    int &ref4 = integer_array[5]; // integer_array[5]도 int타입 lvalue. 배열의 원소 array[5]를 가리킴.
 
    widget w;
    int &ref5 = w; // w는 int타입 lvalue로 암시적 변환됨. 변수 global을 가리킴.
 
    const char (&ref_str)[6] = "hello"; // string literal "hello"는 const char [6] 타입 lvalue. 해당 string literal을 가리킴.
}

상수 타입을 가리키는 참조자는, 말씀하신 대로 rvalue에 묶일 수 있습니다.
제가 이미 말씀드렸다시피 다소 변칙적인 문법인데, C++ 표준에 이런 용법을 정당화하는 항목이 따로 있어요. 자세한 설명은 생략하도록 하죠.

int return_an_int(void){
    return 10;
}
 
void some_function(const int &param){
    return; // do nothing
}
 
void example2(void){
    int local;
    const int &cref1 = local; // lvalue에 묶는 건 여전히 문제 없음.
    // cref1 = 10; // Error: 당연히 상수 참조자로 값을 바꿀 수는 없죠.
 
    const int &cref2 = 10; // 역시 문제없음. 상수 참조자는 rvalue에 묶일 수 있습니다.
    // cref2 = 20; // Error: 역시 안 됩니다.
 
    const int &cref3 = return_an_int(); // 마찬가지로 rvalue. 마찬가지로 문제 없음.
    // cref3 = 30; // Error: 왜 안 되는지 설명드릴 필요가 있을까요?
 
    some_function(local); // 그래서 이런 용법이 가능한 거죠.
    some_function(10); // lvalue를 넘겨도 되고, rvalue를 넘겨도 되고.
    some_function(return_an_int()); // 마찬가지.
}

제가 상수 타입을 가리키는 참조자라고 말씀드린 점 주목하세요.
다른 때는 문제가 안 되는데 포인터에서 문제가 됩니다.
포인터 선언에서의 const의 용법은 기초 C++ 선언 문법 중에서 가장 혼란스러운 문법 중 하나거든요.

결론부터 말씀드리면, 포인터 자체가 상수인 경우, 포인터가 가리키는 대상이 상수인 경우로 나누어 생각해야 합니다.
코드로 보여드리죠:

void example3(void){
    int local1; // 두 개의 비상수 변수.
    int local2;
 
    int *ptr1 = &local1; // 비상수 변수를 가리키는 비상수 포인터
    *ptr1 = 10; // 가리키는 대상의 값을 바꿀 수 있고
    ptr1 = &local2; // 다른 대상을 가리키도록 바꿀 수도 있습니다.
 
    const int *ptr2 = &local1; // 상수 변수를 가리키는 비상수 포인터
    // *ptr2 = 20; // Error: 가리키는 대상의 값을 바꿀 수는 없지만
    ptr2 = &local2; // 다른 대상을 가리키도록 바꿀 수는 있습니다.
 
    int * const ptr3 = &local1; // 비상수 변수를 가리키는 상수 포인터
    *ptr3 = 30; // 가리키는 대상의 값을 바꿀 수는 있지만
    // ptr3 = &local2; // Error: 다른 대상을 가리키도록 바꿀 수는 없습니다.
 
    const int * const ptr4 = &local1; // 상수 변수를 가리키는 상수 포인터
    // *ptr4 = 40; // Error: 기리키는 대상의 값을 바꿀 수도 없고
    // ptr4 = &local2; // Error: 다른 대상을 가리키도록 바꿀 수도 없습니다.
}

Scott Meyers의 책 『Effective C++ 3판』 항목 3에서 위 내용을 간단히 요약해서 설명합니다.

Scott Meyers (곽용재 역) wrote:
변덕에 일평생을 바쳐온 사람이 만든 문법처럼 보이지만 그렇지 않습니다. 잘 보세요. const 키워드가 *표의 왼쪽에 있으면 포인터가 가리키는 대상이 상수인 반면, const 가 *표의 오른쪽에 있는 경우엔 포인터 자체가 상수입니다. const 가 *표의 양쪽에 다 있으면 포인터가 가리키는 대상 및 포인터가 다 상수라는 뜻이죠.

질문자님의 예제로 돌아가 봅시다.

string literal "hello"const char [6]타입 lvalue입니다.
const char, 즉 상수형 문자가 여섯 개 있는 배열이라는 거죠.
array-to-pointer 변환에 의해 위 표현식은 const char * 타입 rvalue로 변환됩니다.

위에서 포인터 선언에 const가 어느 자리에 들어갈 때 어떤 의미를 갖는지 말씀드렸던 것을 다시 살펴보세요.
const char *는 상수형 문자(const char)를 가리키는 비상수 포인터입니다!

void example4(void){
    const char *str = "hello"; // 상수 char를 가리키는 비상수 포인터
    // *str[0] = 'm'; // Error: 가리키는 대상의 값을 바꿀 수는 없지만
    str = "world!"; // 다른 대상을 가리키도록 바꿀 수는 있습니다.
}

다시 말해서, const char *에서 const의 의미는
포인터가 상수라는 게 아니라, 가리키고 있는 문자가 상수라는 것입니다.
string literal을 이루는 문자는 변경 불가능하다는 제약으로부터 온 것이지요.

상수 타입을 가리키는 참조자를 만들기 위해서는, 포인터 자체가 상수여야 합니다. 물론 이 경우에는 포인터가 가리키고 있는 (string literal의) 문자들 역시 상수입니다. 그래서 제가 위 답변의 3.에서 보여드린 선언이 나온 것입니다.

void example5(void){
    const char * const str = "hello"; // 상수 char를 가리키는 상수 포인터
    // *str[0] = 'm'; // Error: 가리키는 대상의 값을 바꿀 수도 없고
    // str = "world!"; // Error: 다른 대상을 가리키도록 바꿀 수도 없습니다.
 
    const char * const (&ref1) = "hello"; // 상수 char를 가리키는 상수 포인터를 가리키는 참조자
    const int (&ref2) = 10; // 조금 복잡하지만, 그렇게 해야 잘 됩니다.
}
익명 사용자의 이미지

확인이 늦어져서 죄송합니다;; 일단 답변 정말 감사합니다.
제가 아직 많이 부족해서 지금 당장 답변해주신 내용이 완전하게 이해된다고는 할 수 없으나 제가 이해한 것과 남은 의문점에 대해 말씀드릴게요.

일단 일반적으로 const를 사용하는 경우와 포인터 선언에서 const를 사용하는 경우를 조금 달리 생각해야 한다는 것 같네요. 뭐..한국말로 번역했을 때 괜히 더 복잡해지니까 그냥 영어로 명칭을 설명하자면 const 키워들 맨앞에 두느냐 아니면 변수명 앞에 두느냐에 따라 pointer to constant, constant pointer 이렇게 구분되는 거 맞습니다.

그런데 C언어와 다르게 C++은 참조자라는 개념이 나오죠. 참조자는 음..포인터도 아닌 그냥 선언된 변수에 대한 별칭..쉽게 말해 대타 이런 것 같네요.

현재 저는 상수 문자열에 대한 참조자를 선언하고 싶은 것이고요.
여기서 일단 참조자는 조금 있다가 살펴보고 문제를 축약해서 상수 문자열을 가리키는 포인터를 선언한다고 합시다. 그렇다면 저는 char형 변수 포인터 하나를 선언할 것입니다. 그런데 변수포인터가 상수문자열을 가리켜야 한다는 것입니다. 그래서 변수 포인터보고 상수 문자열을 가리킬 포인터가 되어줘라고 하기 위해 선언 맨 앞에 const를 붙여줍니다.

그런데 여기서 상수 문자열을 가리킬 것이라해서 포인터를 상수화시키지는 않았습니다. 그럴 이유가 없습니다. 그렇다면 이제 본 문제로 돌아가보면.. 제가 앞앞에서 참조자에 대해 말씀드릴 때 포인터가 아니고 그냥 대타라고 표현했습니다.

어떤 타입으로 선언된 변수이든 참조자는 해당 변수를 대신해서 쓸 수 있는 개념이라고 알고 있습니다.
그렇다면 참조자가 참조할 대상이 포인터이긴한데 그게 constant pointer일 필요가 있을까요?

요점만 말씀드리자면 참조자가 참조할 대상이 상수문자열에 대한 주소여서 pointer to constant type이어야겠네요. constant pointer tpye이 아니라 pointer to constant의 속성을 가지기만 하면 되는데 참조자의 참조 타입이 constant pointer type이어야 한다는 부분이 뭔가.. 제 머리로는 명쾌히 받아들이질 못하는 것 같습니다..

shint의 이미지

어셈블리 소스 코드로 보입니다. 그거도 참고 해보 세요.

----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.

매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.

각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.