C++ 에서 this 는 lvalue 가 되지 못하나요?

winner의 이미지

Essential C++ 를 보고 있습니다.
이상하게 참 맘에 안 드는 source 의 연속입니다.

그런데 그 중 재미있는 source 가 있는 것이

Member 함수를 호출하면서 자신의 주소를 참조로 매개변수로 넘깁니다.
그러면 이 매개변수는 this 와 동일하게 됩니다.

왜 이렇게 하나하고 this 로 바꿔봤더니 invalid lvalue 라고 나오는군요.

즉 this 는 대입문 좌측에 올 수 없다 이건데...

저로서는 합당한 이유를 모르겠더군요.
delete this 까지 가능한 무적의 C++(물론 가장 뛰어난 언어라는 뜻은 아닙니다.)가
왜 이걸 막아놓았을까요?
이것이 가능하다면 주어진 source 가 좀더 다듬어질 수 있을 것 같습니다.

또하나 궁금한 것이 pointer 가 아직 null pointer 일때
이 pointer 를 이용해서 member function 을 호출하는 것은 가능한가요?

물론 정적 member 함수를 말하는 것이 아닙니다.
Runtime 에 호출위치를 판독하는 virtual 함수는 물론 불가능할 것입니다.
아직 할당되지 않은 객체니까 객체의 실질형태에 따라 판독하는 virtual 함수는 불가능하겠죠.

제가 궁금한 것은 다음과 같은 source 입니다.

class a
{
    int i;
    public:
        void printi(void)
        {
            if (this) // this 가 null pointer 라면 밑의 문장은 실행되지 않습니다.
                cout << i;
        }
};

g++ 의 경우 일단 오류는 안 냅니다만 표준에 이런 것은 명시되지 않는지 궁금하네요.

Java 의 경우 null 의 참조자는 이런 호출을 하면 NullPointerException 을 냅니다.
Java 는 기본이 C++ 의 가상함수와 같으니까 그렇겠죠.
하지만 혹시 가능한 경우가 있을까요? static method 는 물론 안 됩니다.
this 를 이용할 수 없으니까요...

final class 로도 안되고...

익명사용자의 이미지

첫번째는 무슨 경우인지 정확히 모르겠습니다.

this 는 lvalue 가 될 수 없습니다.

두번째도 무슨 말인지 잘 모르겠습니다.

널포인트에서 함수를 못 부릅니다.

위의 소스가 의미가 없긴 해도 문법이 틀린 건 아닙니다.

정의 그대로 this 는 비정적 멤버함수에서 non-lvalue 인

호출된 멤버함수의 객체 주소값입니다.

only2sea의 이미지

네, this는 lvalue가 될 수 없습니다.

두 번째의 경우에는 말씀하신대로 virtual member function인 경우에는 세그멘테이션 오류가 나지만 정적 멤버 함수에서는 단지 첫 번째 인자로 this를 넘겨주는 일반 함수와 마찬가지로 문제없이 사용하실 수 있습니다. 보여주신 소스 코드에서는 printi가 virtual 함수가 아니군요.

표준에서 어떤지는 잘 모르겠습니다. 제가 그다지 도움이 되는 답변을 한 것은 아니군요.

아마록에서 가사와 앨범 표지를... http://kldp.net/projects/amarok-script/ 블로그: http://turtleforward.blogspot.com

winner의 이미지

정적 member 함수라고 하셨는데 static 을 말씀하시는 것인가요?
아니면 주어진 source 가 문제가 없다는 뜻인가요?

Member 함수호출구조를 맞추기 위해서 this 를 lvalue 가 될 수 없게 했다고 이해하면 맞을까요?
즉 this 가 lvalue 가 되기 위해서는 member 함수에 넘겨줄 자기자신을 이중 pointer 형태로 넘겨주지 않으면 안될 것입니다.
a *pa = new a;
a->printi();
라고 할 때 printi 내에서 this 에 무엇인가를 대입하려면
이것은 printi(&pa) // 이중 pointer a **
의 형태로 변형이 가능해야 하지만

a aa;
aa.printi();
라고 할 때 형식을 맞출려면 printi(&&aa) 가 되어야 하는데
&aa 는 lvalue 가 아니니 &&aa 가 불가능할니까요.

그렇다고 pointer 를 통해서 호출되는 member 함수와 객체를 통해서 호출되는 member 함수를 분리해서
두개로 만드는 것 또한 낭비일 것 같군요.

하지만 그렇다면 논리적으로 Java 는 this 가 lvalue 가 될 수 있을텐데...
해보면 final 이라서 역시 불가능...

익명사용자의 이미지

뭔가.. C++ 접근을 잘못 하시는것 같네요.

this 는 const 포인터 입니다. 자바는 포인터 개념이 없고 모든 객체가 레퍼런스이지 않나요?
C++은 포인터와 레퍼런스 두가지 방식을 모두 제공됩니다. 따라서 this 포인트를 자바의 객체 레퍼런스와 바로 비교하시는건 바른 접근이 아닙니다.

C++에서는 포인터를 통한 처리가 그 기본이기 때문에 this가 포인터로 제공되는것이고,
객체 내에서 자신의 포인터값을 바꾼다는게 의미가 없기 때문에 const 제약이 따르며,
lvalue 가 될 수 없는것 뿐입니다.

어떠한 구현방식을 생각하시는지 모르겠지만 자바에서 구현한 방식이
C++에서 동일하게 구현되지 않습니다. 다른 접근이 필요한 경우가 많습니다.

함수의 인자로 자바는 객체의 경우 객체 레퍼런스 한 가지라면
C++은 객체 값이 복사된 새로운 객체(값이 복사됨), 레퍼런스, 포인터 인자와 같이 여러가지 방식으로 구현될 수 있습니다. 선택의 폭이 넓어지면서, 복잡성은 증가할 수 있겠죠.

마지막 예제의 this 포인터 비교는 의미 없습니다.
이유는 호출 시 this 포인터가 null 이라면 호출 자체가 되지 않습니다. 포인터 참조 오류가 발생합니다.
객체가 존재하지 않는 경우이기 때문입니다. c++ 에서도 예외를 발생시킬 수 있습니다.

마지막으로, C++과 Java는 다르니 언어적 차이에 대한 이해를 하셔야 할거 같습니다.

winner의 이미지

우선 Java 의 참조는 C++ 입장에서 보기에 말만 참조지 C++ 의 pointer 와 유사해보입니다.
그래서
String abc = null;
abc = "abc";

와 같은 두 문장이 가능하겠지요.

C++ 의 참조는 초기화선언문에서 지정된 객체 이외에는 다른 참조를 못하지 않습니까?
저렇게 같은 참조자로 다른 객체를 지정할 수 있는 것은 C++ 에서는 pointer 입니다.

그러니 null 을 가지고 method 를 호출하면 내는 예외가 NullPointerException 이겠지요.
C++ 는 만일 pointer 가 가리키는 객체를 역참조해서 참조자로 지정하지 않는다면 null 을 참조할 일도 없습니다.
따라서 참조자를 넘겨받는 함수는 참조자의 null 검사를 묵시적으로 하지 않는 것이 관례입니다.
(Java 의 참조는 참조의 탈을 쓴 pointer... -_-.)
이렇게 쓰고 보면 악담처럼 보이지만 Java 의 참조자는 C++ pointer 에서 표기법이 변경되기 때문에
bug 발생이 줄어든다고 생각하며 좋은 점이라고 생각합니다.

다음 이야기로 넘어가서
제가 알고 있기로 C++ 에서는 delete this; 가 허용됩니다.
그렇다면 delete 를 한 후 다른 객체를 할당하는 것을 같은 member 함수에서 하는 것은 어떨까요?

Essential C++ 의 예제에서는 Binary Tree 의 node 삭제가 나옵니다.
예를 들어 최상위 root node 를 제거한다면 root 의 좌우측 중 하나가 root 되고
나머지는 terminal 의 자식이 되게 하고 있습니다.
root 는 BTNode * 인데 root->remove() 하면 될 것으로 보입니다만
실제로는 root->remove(root) 가 됩니다.
여기서 remove 는 BTNode *& 를 매개변수로 받죠.
그래서 원래 root 가 가리키는 node 를 제거하고 root 좌측 subtree 를 우측 node 의 최좌측 terminal 의
좌측에 연결한 후 바꿀 수 없는 this 를 넘겨받은 매개변수를 통해 원래 root 의 우측 node 로 바꾸고 있습니다.
이 문장이 끝나면 root 는 여전히 이 Binary Tree 의 root 로 남게 됩니다.
삭제되기 전의 원래 root 의 우측 node 를 가리키면서 말이죠.
하지만 root->remove(root) 는 뭔가 이건 좀 이상하다는 느낌이 오지 않습니까?

만일 this 가 lvalue 가 될 수 있고, Java 의 참조를 C++ 의 pointer 와 동일하게 본다면
root->remove() 만으로도 가능할텐데 말이죠...
(하지만 이건 제가 생각해도 극단적인 예이긴 합니다.)

어찌되었든 불가능하다면 public static member 함수가 가장 괜찮을 것 같네요.
static member 함수 호출시 앞에 class 이름을 적어야 하는 것이 눈에 거슬리긴 합니다만...
아니면 friend?...

익명사용자의 이미지

root->>remove(root) 는 root 포인터 변수에 저장된 주소를 바꾸기 위해서 필요합니다.
this 라는 포인터는 단지 해당객체가 할당되어 있는 주소값을 확인할 뿐이지 해당 객체를 저장하고 있는 포인터 변수를 참조하지 않습니다.

예제의 경우 root 포인터 변수가 가르키고 있는 저장 주소값을 바꾸어야 하는데, 객체내에서 알아서 할 수 없는 것이지요.

winner의 이미지

똥싸면서 떠올랐습니다.

BTNode *pa = new a;
BTNode *pb = pa;

remove 내에서 this 에 대입을 할 경우
pa->remove();

를 하면 pb 는 변해야 할까? 변하지 말아야 할까?

만일 변해야 한다면
pb 가 BTNode * const pb = pa; 였다면...?

내 자신이 무엇인지 남에게 알릴 수는 있어도(this 의 rvalue 활용),
외부의 남이 나라는 존재를 다른 존재로 인식시키는 것은(this 의 lvalue 활용)
기본적으로 불가능한 것이 옳다는 것인 것 같습니다.(특별한 매개변수가 없다면)

그래도 역시 root->remove(root); 는 뭔가 중복인 것 같다는 느낌을 지울 수가 없군요.
취향문제일 수도 있겠습니다만...

그나저나 두번째 예제에 대해 답을 기다립니다만...

only2sea 님 답글 역시 이해에 도움이 많이 되었습니다.

only2sea의 이미지

네, printi에 대하여 제가 콕 찝어서 클래스의 static 멤버 변수라고는 하지 않았습니다. 저는 단지 printi가 virtual member function이 아니라고 한 것이지요. 즉, static member function이라는 것은 각 인스턴스에 상관없이 특정 클래스에 붙어 있는 멤버 함수지요. printi는 그런 static member function이 아닙니다. 그렇지만 virtual member function은 아니지요. 어쨌든 printi의 호출이 컴파일 타임에 결정되는 것은 분명합니다. 그것은 포인터를 통하여 member function을 호출한다고 해도 첫 번째 인자로 this 포인터를 넘겨주는 함수와 비슷하게 동작한다는 것입니다. 즉, 예제에 있는 내용은 문제없이 쓸 수 있습니다. 그러나 이것이 표준에서도 허용하고 있는 부분인지는 잘 모르겠습니다. 그래서 제 대답이 별로 의미 없는 것 같다고 한 것이구요.

그리고 익명사용자님께서 말씀하셨듯이 this 포인터는 const 포인터이기 때문에 포인터가 가리키는 것의 값을 바꿀 수는 있어도 포인터의 값은 바꿀 수가 없게 되어 있습니다. 그래서 lvalue가 될 수 없는 것이죠. C++은 이런 체크가 엄격한 편인 언어거든요. 멤버 함수 선언부 뒤에 const가 하나 더 붙어 있으면 this 포인터가 가리키는 값까지 바꿀 수 없는 것이 되어 버리죠.

p.s. C++의 이런 const는 꽁수를 쓰면 바꿀 수 있긴 합니다만, 굳이 그렇게 까지 해서 바꿀 필요는 없겠죠. 어차피 this는 값이 복사 된 것일 것이니 이것을 바꾼다고 해서 바깥에서 할당되어 있던 객체 포인터가 바뀌는 것은 아니지요.

아마록에서 가사와 앨범 표지를... http://kldp.net/projects/amarok-script/ 블로그: http://turtleforward.blogspot.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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.