[완료] C++에서 dynamic_cast 에 관한 질문입니다.

parkon의 이미지

안녕하세요,

현재 dynamic_cast 연산자를 쓰는 제 코드는 잘 돌아 갑니다.
근데 제 일이 연산속도에 민감한 일이어서 느리다는 dynamic_cast 대신 좀 더
연산이 빠른 방법이 있을까 하고 찾고 있습니다.

우선 제 상황을 대충 정리해보면
기본 베이스 클래스인 Object, 펑션 기능에 대한 인터페이스를 가진 추상 클래스 Functor,
이 둘을 부모로 가지는 MyObject, 그리고 Object 인스턴스들의 컨테이너 클래스인 Container가 있습니다.

class Object { ... };
 
class Functor {
   virtual double Eval(double) = 0;
};
 
class MyObject : public Object, public Functor
{ 
   double Eval(double);
};
 
class Container {
   Object* At(int n);
};

위에는 하나만 썼지만 MyObject처럼 Object와 Functor 이 둘을 부모로 가진 여러 클래스들이 있습니다.
이제 저 At(n) 했을때 항상 MyObject처럼 Functor를 상속받은 클래스의 인스턴스 포인터를
주는 MyContainer란 클래스가 있습니다. 즉,

class MyContainer : public Container {
   double EvalAt(int n, double x);
};
 
double MyContainer::EvalAt(int n, double x) {
   Functor* f = dynamic_cast<Functor*>(At(n));
   return f->Eval(x);
}

이렇게 하면 잘 돌아 갑니다. 결과값도 원하는 대로 나오고요.

이제 잘 돌아가는 걸 확인했으니까 좀 더 속도가 빠르게 하고 싶은데,
저 dynamic_cast를 static_cast로 하면 컴파일러 에러가 뜨고 (당연한 거겠죠),
reinterpret_cast로 바꾸면 뭔가 돌아가기는 하는데
잘못된 결과값을 출력합니다.
저 문장을
   Functor* f = (Functor*) At(n);

으로 바꾸어도 reinterpret_cast로 한 경우와 똑같은 결과가 나오고요.
제 고민을 해결할 어떤 좋은 수가 없을까요 ?

익명 사용자의 이미지

Container라는 이름을 보니 용도가 짐작되는데...

std::vector에다가 functor를 넣어두고 n번째 functor를 불러다 쓰면 되겠네요.

익명 사용자의 이미지

1. Object 클래스는 가상함수를 가지고있나요?
2. Container에 Object 타입으로 저장하지 않고 Functor타입으로 저장하고 빼서 쓰면 안되나요? 위 코드에서는 Functor의 인터페이스만을 사용하기 때문에 물어보는 것입니다.

parkon의 이미지

제 상황이 거의 모든 클래스는 무조건 Object를 첫번째 부모로 삼아야 합니다.

class AnyClass : public Object, public Other, public Other2, ... {...}

사실 위처럼 정의된 클래스들이 아주 많이 있습니다. 그것들 중 일부는 저 펑터를 부모 중 하나로 가지고요.
위의 MyContainer는 그러한 (Object와 Functor를 부모로 가지는) 녀석들의 컨테이너 클래스입니다.

그리고 Object 클래스는 물론 가상 멤버 함수들을 여러개 가지고 있습니다.
그래서 dynamic_cast는 아무 문제 없고요.

semmal의 이미지

공부하는데 정도, 사도, 마도가 있습니다.

책에서 하라는데로 하고 하지말라는 건 안하는게 정도입니다. 처음에는 왜 이렇게 해야하는지 모르겠지만, 나중에 경험이 쌓이다보면 이렇게 하는게 가장 빠르다는 걸 알게됩니다.
제대로된 추상화, DB의 정규화, 수학과 알고리즘에 대한 공부, 등이 좋은 예라고 볼 수 있습니다.
이런 걸 익히다보면 느리지만 차츰 확실한 고수가 됩니다.

사도는 목표를 명확히 알고 있을 때, 하지말라는 것도 하면서 가는 겁니다. 일이 잘되면 다행이지만, 일이 잘못된다면 더 문제가 발생할 수 있는 형태지요.
무엇보다 속도만 빠르게 하는 코드 또는 용량만 매우 작아지는 코드, DB의 역정규화를 비롯한 여러 테크닉이 적절한 예지요.
이런 걸 익히면 빠르게 문제를 해결하지만, 문제가 조금이라도 어긋나면 오히려 전체를 망칩니다.
사도를 따라도 하다보면 얻는게 있지만, 결국에 정도에서 멀어져서 오히려 고수가 되기 힘들 수도 있습니다.

마도는 가야할 길도 제대로 모르면서, 호기심으로 옆길만 기웃거리는 겁니다. 애초에 길을 잘못들었기 때문에 책에서 알려주고자 하는 의의도 찾지못하고, 목표도 달성하지 못합니다.
문제와 전혀 상관없는 곳에 집중하면서, 결국 자신의 방법을 해결한다고 해도 별 의미없는 일을 반복하는 겁니다.

C++의 virtual이 느려서 싫다면, C나 Fortran, 심지어는 Assembly로 프로그램 짜면 됩니다.
하지만 그 정도로 느려서 싫은 건 분명 아닐진데 그것에 집중하고 있다면, 이 역시 문제를 잘못 파악하고 있는 겁니다.
고생은 고생대로 하고, 성공해도 얻는게 별로 없으며, 실패하면 아무것도 얻지 못하는게 마도입니다.

키보드에 손을 올리기전에 먼저 생각해보세요.

'내가 정확히 무엇을 하려고 하는가?'

------------------------------
How many legs does a dog have?

parkon의 이미지

semmal님께서 말씀하신 내용이 dynamic_cast를 피하기 위해 다른 꼼수를 써서 바꾸려고 하지 말란 말씀이신가요 ?

만일 그렇다면 제가 지금 하는 일이 몬테 카를로 계산에 이용될 라이브러리 제작인지라
빈번하게 호출되는 루틴에는 dynamic_cast를 허용할 입장이 아닌것 같아서요.

사실 정히나 위의 제 질문에 만족스러운 답을 못구한다면
좀 끔찍하긴 하지만
저 Object 클래스에 Functor 인터페이스를 몽땅 집어 넣어서 아예 캐스팅 연산자를 안 써도록 해야 하나
고민 중에 있습니다.
근데 저 Functor 클래스가 고유의 멤버 변수들도 가지고 있어서 이 방법은 여러모로 맘에 안들긴 해요.

semmal의 이미지

Object나 Functor를 써야하는 이유가 있는지부터 따져보세요.

속도가 중요하다면 쉽게 Object나 Functor를 쓰는 OOP 방식을 포기하면 됩니다.
속도가 필요한 부분에는 C 스타일로 함수 호출만 사용하면 더 빠르게 동작하겠죠.
그것도 부족하다면 다른 언어를 쓰면 되구요.

------------------------------
How many legs does a dog have?

sblade의 이미지

reinterpret_cast 는 안됩니다. 잊어버리세요.

안되는 이유는, polymorphic 한 instance 를 downcasting 할때, dynamic_cast 는 input pointer 에 offset 을 얼마나 주어야 하는지 class hierarchy 를 뒤져서 계산한 후 output pointer 를 내놓습니다. 이게 runtime information 이 필요한 이유이고 속도가 느린 이유입니다.

reinterpret_cast 는 그냥 bit-by-bit mapping 입니다. offset 을 계산하지 않고 해당 자리에서부터 그대로 mapping 하기 때문에 이상한 결과가 나올 수밖에 없습니다.

먼저 진짜 느린 속도의 주범이 dynamic_cast 인지 살펴보세요. profiling 을 하지 않고 optimization 을 하려고 하면 안됩니다. 느린 속도의 주범이 dynamic_cast 가 아닐 가능성이 훨씬 높습니다.

그리고 프로그램의 type hierarchy가 상당히 이상한데, "Object" 클래스가 꼭 필요한가요? "Functor" 도 "Object" 를 상속받나요? 프로그래밍 언어를 디자인 하는 경우가 아니면 "모든 것의 부모" 가 필요한 경우는 거의 없습니다. 아마 "Functor" 를 "Object" 로부터 분리하면 문제가 훨씬 쉬워질 겁니다. 관계가 별로 없는 것들을 프로그래밍의 편의를 위해 관계지으려고 하는게 보통 문제의 시작입니다.

그리고 저런 경우의 container 는 일반적으로 template 을 이용해 만드는게 옳은 솔루션일 경우가 많습니다.

windowsprogrammer의 이미지

글의 모든 내용에 동의합니다.
(그런데 글에 무슨 영어 단어가 이렇게 많나요 ㅎㅎ)

sblade의 이미지

다시 읽어보니 안써도 되는 영어가 좀 있네요.
영어는 영어로 한글은 한글로 쓰자가 원칙이긴 한데, 프로그래밍을 미국에서 배워서 많이 통용되는 한글 용어를 제가 잘 모르는 것 같습니다.
다음부터는 좀 더 주의해서 써야겠습니다. :-)

parkon의 이미지

sblade님 답변 덕분에 다운캐스팅 원리를 조금이나마 짐작하게 되었네요.
제가 맡은 일이 기존의 어떤 라이브러리를 이용해서 다른 라이브러리 제작인데요,
이 기존 라이브러리가 C++인데 자바의 CObject와 비슷한 거의 모든 클래스의 기본 클래스를 가지고
(그걸 여기서 Object class라 제가 불렀습니다)
다른 클래스들이 이 클래스를 첫째 부모로 가진다는 가정하에 만들어 진거라
이건 꼭 필요한 거고요.

제가 10~20개의 데이타 타입을 다루는 데요, 그 자료 구조형에 따라
Object < ObjA < ObjB < ObjC < ...
ObjA < Container
Container < ContainerA < ...
이런 식으로 클래스들을 구성했습니다.

근데 이들 중 어떤 녀석들은 펑터가 아니고,
어떤 녀석들은 1차원 펑터이고, 어떤 녀석들은 1차원 펑터들의 집합으로서 하나의 2차원 펑터이고
어떤 녀석들은 비슷하게 3차원 펑터,
또는 2차원 펑터는 아니지만 1차원 펑터들의 집합 등의 속성을 갖습니다.

그래서 이들 속성에 따라 VF1 < VF2 < VF3, VF1 < VF1s 등의 추상 클래스들을 만들었고
(다중상속의 골치아픈 문제들을 피하기 위해 이들은 Object로부터 상속받지 않았습니다)
이들로부터

class MyObjA1 : public ObjA, public VF1 {...};
class MyObjB1 : public ObjB, public VF1 {...};
class MyObjB2 : public ObjB, public VF2 {...};
class MyObjC2 : public ObjC, public VF2 {...};
class MyObjA1s : public ObjA, public VF1s {...};
...

이런 클래스들이 만들어졌고 또 위 본문에 제가 적었던 것처럼 (조금 다르게 적어보면)
class MyCon3 : public ObjA, public VF3 {
public:
   MyObjA* At(int n); // 이것들은 명시적으론 ObjA의 파생 클래스의 인스턴스이지만 실제론 ObjA(또는 이의 파생 클래스)와 VF2의 파생 클래스임)
   double EvalAt(int n, double x, double y) {
      VF2* f = dynamic_cast<VF2*>(At(n));
      return f->EvalF2(x,y);
   }
private:
   MyContainer fObjs; // 컨테이너 멤버 변수
}

이런 식이 나온거죠.

정리하자면 여러 개의 데이타 자료 구조형이 있고
여러개의 속성이 있는데
이 자료 구조형과 속성이 딱 1대 1로 매칭이 안되고 다양한 조합이 존재한다는게
제가 애먹고 있는 근본 원인인 것 같아요.
그래서 템플리트도 제겐 해법이 아닌것 같구요.

parkon의 이미지

아 그리고 아직은 라이브러리 제작 단계이기 때문에 아직은 속도가 안 중요합니다.
뭐 수만 수십만번 정도의 연산이야 이러나 저러나 순식간에 해치우니까요.
단지 나중에 다 만들어지고 나면 그땐 속도가 아주 중요한 요소로 등장할 텐데
지금 클래스 설계가 그 때의 요구에 부응할 수 있을지가 관건이고
나중에 가서 클래스 설계를 밑바탕부터 싹 다 바꾸는 불상사를 없애기 위해
위 질문글 올린 것입니다.

parkon의 이미지

여기에 글 올리고 여러 댓글들을 읽다보니 제가 하는 일을 다시 정리할 수 있는 계기가 되는군요.

우선 제 질문은
static_cast를 쓸 수 없고, reinterpret_cast나 C스타일 캐스팅이 오작동을 하는 환경에서
dynamic_cast를 대신할 수 있는 빠른 방법이 있는가 하는 거였는데
위 댓글들에서 유추해 보건데 정답은 없다는 거죠 ?

지금 와서 제 자료 구조형들을 다시 자세히 살펴보니
몇개의 서비스 클래스와 concrete 클래스들을 추가하고
몇번의 코드 중복을 감수한다면
(그리고 새로운 자료 구조형 추가에 대한 유연성을 좀 희생한다면)
다행히 저 dynamic_cast를 안 쓰게 만들수도 있을듯 합니다.
즉 연산속도에 중점을 두더라도 막판에 제 클래스 구조 전체를 다시 뒤엎어야 하는 불상사는 일어나지 않을것 같네요.
그래서 일단은 저 dynamic_cast 쓰는 루틴을 그냥 둬도 괜찮을듯 싶구요.

그래서 일단 제 글의 제목에 "완료"를 붙이려 합니다.
혹시 위 질문에 다른 생각이 있으신 분 계시면 추가 댓글 달아주시면 좋겠구요.

제 글을 읽어주시고 댓글 주신 모든 분들께 감사 인사 올립니다... ^^

kukyakya의 이미지

기존의 소스코드를 수정할 수 있다면 llvm의 dyn_cast 템플릿을 사용해보시는건 어떤가요? 직접 써보진 않았지만 가져다 쓰기에는 편하게 헤더 파일 하나로 구성되어있던걸로 기억합니다.

익명 사용자의 이미지

처음부터 다시 짠다면...

Qt를 기반으로 객체들을 QObject로 만들어서 qobject_cast를 사용할 수도 있겠죠...

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.