키워드 'virtual' 심화 탐구

|

C++에서 자주 쓰이는 virtual 키워드에 대해서 알아보도록 하겠습니다.

먼저 가장 대표적인 특징으로는 오버라이딩이 될 수 있습니다.


오버라이딩

상속시 `virtual` 함수만 정상적으로 오버라이딩(overriding)될 수 있습니다.

다음 예제를 살펴보도록 하겠습니다.

class Parent {

 public:
  void hello() {
    printf("Hello. This is the parent.\n");
  }
};

class Child: public Parent {
 public:
  void hello() {
    printf("Hello. This is the child.\n");
  }
};

여기서 만약 다음 코드를 호출하면 결과는 어떤게 나올까요?

  Child c;
  c.hello();

결과는 Hello. This is the child.가 화면에 출력될 것입니다. 결과만 봐서는 굳이 virtual 키워드를 사용하지 않더라도 정상적으로 오버라이딩된 것처럼 보입니다.

하지만, 만약 다음 코드처럼 부모 클래스의 참조를 이용해서 호출을 하면,

  Child c;
  Parent& ref = c;
  ref.hello();

결과는 Hello. This is the parent.가 나옵니다. 부모 클래스의 hello() 함수가 virtual이 아니기 때문에 이런 결과가 발생하게 됩니다.

즉, 단순하게 사용할 때는 virtual 키워드를 사용하지 않더라도 마치 정상적으로 오버라이딩된 것처럼 보였지만, 실제로는 각 함수들이 정상적으로 오버라이딩이 된 것이 아니라 부모의 함수와 자식의 함수가 각각 따로 존재했을 뿐이라는 것을 알 수 있습니다.


vtable

virtual 키워드를 사용하지 않은 경우에는 부모의 함수와 자식의 함수가 각각 개별적으로 하드코딩되어져서 두 함수 모두 존재하게 됩니다. 이 때 함수의 이름은 컴파일 타임 타입에 맞춰져서 각각 다른 이름으로 저장이 됩니다.

하지만 virtual 키워드를 사용한 경우에는 해당 함수를 vtable(Virtual Table)이라고 부르는 특수한 메모리 영역에서 함수 주소값을 관리하도록 합니다.

즉, 함수가 실행될 때 vtable을 참조하여 실제로 올바르게 오버라이딩된 함수를 찾아서 실행해주기 때문에 virtual 키워드를 사용해야만 ‘정상적으로 오버라이딩 되었다’라고 할 수 있습니다.


virtual 키워드에 대한 논쟁

그래서 많은 프로그래머들은 모든 함수들을 virtual로 선언하는 것을 권하고 있습니다. 그렇다면 애시당초 virtual 키워드를 사용하지 않더라도 디폴트로 virtual처럼 취급하면 되지 않냐는 의문점이 남습니다.

Java 언어는 모든 메소드를 `virtual`로 취급하고 있습니다.

virtual 키워드가 만들어진 배경에는 vtable을 사용하는데 드는 오버헤드때문이었습니다. 함수 주소값을 참조해서 실제 함수를 찾는 과정이 필요하기 때문에 성능 저하가 발생할 수 밖에 없었고, C++ 언어를 디자인하던 사람들은 이러한 이유 때문에 프로그래머에게 선택권을 주는 것이 더 낫다고 판단했습니다. (실제로 virtual 키워드를 사용하기 때문에 발생하는 오버헤드는 무시해도 될만큼 작습니다.)


소멸자의 virtual 필요성

모든 함수를 virtual로 선언하는 것에는 거부감이 있는 프로그래머들도 클래스의 소멸자만큼은 무조건 virtual로 선언해야 한다는 것에는 동의합니다.

만약 소멸자가 virtual이 아닌 경우에는 객체 소멸시 메모리 해제의 일부가 누락될 수 있는 상황이 발생할 수 있습니다.

예를 들면 다음과 같은 상황입니다. 다음 상황에서 Parent 클래스의 소멸자가 virtual이 아닌 경우 문제가 발생합니다.

  Parent* p = new Child();
  delete p;

위와 같은 경우는 ~Parent()는 호출이 되지만, 소멸자가 virtual이 아니면 ~Child()는 호출 되지 않습니다. 즉, Child 클래스의 소멸자에서 이루어지는 메모리 해제 등은 호출이 안되기 때문에 메모리 누수(Memory Leak) 등이 발생할 수 있습니다.

ListView의 가장 아래쪽으로 자동 스크롤 시키기

|

안드로이드 ListView의 가장 아래쪽 아이템으로 자동 스크롤 시키는 코드입니다.


alwaysScroll 옵션 적용

먼저 ListView에 alwaysScroll 옵션을 적용시켜줍니다.

XML 레이아웃내의 ListView attribute에

android:transcriptMode="alwaysScroll"

항목을 추가시키거나, Java 코드에서

listview.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);

와 같이 작성하시면 됩니다.


자동 스크롤 코드

ListView의 아이템이 변경되었다는 이벤트가 왔을 때 아래 코드를 수행하시면, ListView의 최하단 칸으로 강제 이동 시켜줄 수 있습니다.

listview.setSelection(adapter.getCount() - 1);

Git 설명서 - (10) 원격 브랜치

|

원격 브랜치

원격 브랜치는 원격 서버에 있는 브랜치를 말합니다. 물론 로컬에도 서버에서 Pull로 가져온 원격 브랜치 정보가 모두 존재합니다.

원격 브랜치는 ‘(remote)/(branch)’ 형태의 이름을 가집니다. 예를 들어 origin/master와 같은 이름입니다.

다음 예제는 ‘git.ourcompany.com’ 이라는 원격 Git 서버가 있으며, 이 서버로부터 clone 하여 로컬에 저장소를 가지는 경우에 대한 예제입니다.

clone을 하게 되면, 로컬에서는 원격 저장소의 별명을 origin이라는 이름을 기본(Default)으로 붙입니다. 즉, 다음 그림과 같은 형태의 소스 트리를 가지게 됩니다.

image


다른 개발자가 원격 저장소의 master 브랜치에 commit 한 경우

이 경우 개발자간 히스토리는 서로 달라지게 되고 다음 그림과 같은 상태가 됩니다.

image

원격 저장소의 내용을 로컬 저장소에 업데이트하려면 git fetch origin 명령을 사용해야 합니다. 이렇게 하면, 현재 로컬 저장소에 없는 서버의 내용을 모두 내려받게 되고 로컬의 origin/master 포인터를 가장 최신 commit으로 이동시켜줍니다.

image

물론, 로컬에서 기존에 작업하던 내용이 있기 때문에 소스 분기는 이루어지게 됩니다. fetch로 내려받았기 때문에 소스 정합은 수동으로 해주어야 합니다.

fetch로 내려받은 브랜치의 내용을 정합하려면 git merge origin/(브랜치 이름)으로 명령을 내려주면 됩니다.


작업한 내용을 원격 저장소에 Push

로컬 저장소의 내용을 원격 저장소에 업로드할 때는 push 명령어를 이용합니다. git push (remote) (branch)와 같은 형태로 사용할 수 있습니다.

$ git push origin serverfix
Counting objects: 20, done.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (15/15), 1.74 KiB, done.
Total 15 (delta 5), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
 * [new branch]      serverfix -> serverfix

위 명령은 로컬의 ‘serverfix’라는 브랜치를 origin이라는 이름을 가진 원격 저장소에 ‘serverfix’라는 이름의 브랜치로 Push 하는 명령입니다.

Geocoder를 이용한 주소 및 위도/경도 변환 예제

|

안드로이드에서는 위도/경도를 이용해서 주소값을 획득하거나 반대로 주소값을 이용해서 위도/경도를 획득할 수 있는 Geocoder라는 클래스를 제공하고 있습니다.

에제 코드는 다음과 같습니다.

먼저 인터넷이 되어야 하기 때문에 AndroidManifest.xml에 다음 권한을 추가합니다.

Permission 추가

  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.INTERNET" />


GeocodeUtil.java

package snowdeer.utils;

import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import java.util.ArrayList;
import java.util.List;

public class GeocodeUtil {
  final Geocoder geocoder;

  public static class GeoLocation {

    double latitude;
    double longitude;

    public GeoLocation(double latitude, double longitude) {
      this.latitude = latitude;
      this.longitude = longitude;
    }
  }

  public GeocodeUtil(Context context) {
    geocoder = new Geocoder(context);
  }

  public ArrayList<GeoLocation> getGeoLocationListUsingAddress(String address) {
    ArrayList<GeoLocation> resultList = new ArrayList<>();


    try {
      List<Address> list = geocoder.getFromLocationName(address, 10);

      for (Address addr : list) {
        resultList.add(new GeoLocation(addr.getLatitude(), addr.getLongitude());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    return resultList;
  }

  public ArrayList<String> getAddressListUsingGeolocation(GeoLocation location) {
    ArrayList<String> resultList = new ArrayList<>();

    try {
      List<Address> list = geocoder.getFromLocation(location.latitude, location.longitude, 10);

      for (Address addr : list) {
        resultList.add(addr.toString());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    return resultList;
  }
}

Git 설명서 - (9) 브랜치 관리

|

브랜치 관리

git branch 명령어는 아무런 옵션없이 실행할 경우 브랜치의 리스트 및 현재 checkout 되어 있는 브랜치를 보여줍니다.

$ git branch
  iss53
* master
  testing

-v 옵션을 붙여서 실행할 경우에는 각 브랜치의 마지막 commit 메세지를 보여줍니다.

$ git branch -v
  iss53   93b412c fix javascript issue
* master  7a98805 Merge branch 'iss53'
  testing 782fd34 add scott to the author list in the readmes

--merged 옵션이나 --no-merged 옵션을 이용해서 현재 checkout 한 브랜치 기준으로 정합 작업이 이루어진 브랜치 또는 그렇지 않은 브랜치 리스트를 볼 수 있습니다.

$ git branch --merged
  iss53
* master

위에서 ‘*’ 가 붙지 않은 브랜치는 이미 정합 작업이 끝난 브랜치이기 때문에 삭제를 해도 되는 브랜치입니다. git branch -d 명령어로 해당 브랜치를 삭제할 수도 있습니다.

정합이 되지 않은 브랜치의 경우에는 -d 옵션으로 삭제가 되지 않습니다. 이 경우에는 -D 옵션으로 강제적으로 삭제를 할 수도 있습니다.