C++ 에서 헤더 내에 클래스의 선언과 정의가 모두 있는 경우는 무엇인가요?

awidesky의 이미지

C/C++에서는 함수의 선언과 정의를 .cpp, .h에 구분해 놓는다고 배웠습니다.
막상 찾아보면 그렇지 않은 경우가 많은것 같아 햇갈립니다;;
https://github.com/awidesky/cyclone-physics/blob/master/include/cyclone/core.h
여기에서도 Vecter3 클래스의 함수가 다 선언되어 있네요;;
그냥 다 헤더에 때려넣고 파일 개수를 줄이려 한 줄 알았는데... core.cpp도 따로 있더군요(?!??)
https://github.com/awidesky/cyclone-physics/blob/master/src/core.cpp
헤더에 선언부만 넣는 경우와 정의도 다 때려넣는 겅우의 기준(?)이 궁금합니다.

Stephen Kyoungwon Kim@Google의 이미지

정의를 다 넣게 되면 문제가 생길 수 있습니다. core.h를 foo.cpp와 bar.cpp에 각각 include하는 경우를 생각해 보죠. 정의가 foo.cpp에도 bar.cpp에도 각각 들어가게 됩니다. 따라서 나중에 foo.o와 bar.o를 링크하게 되면 multiple definitions로 링킹 타임 에러가 나게 됩니다.

일반적으로는 선언은 .h 파일에, 정의는 .cpp 파일에 넣는 게 좋습니다.

이 코드는 cyclone.h를 여러 개의 cpp 파일에서 인클루드 하게 되면 문제가 생길 것처럼 보이네요.

정의가 .h 파일에 들어가 있어도 안전하거나 그렇게 해야 하는 경우도 몇 가지 있습니다. 주로 컴파일러가 예컨대 bar.cpp를 컴파일하는 동안 선언 이상의 것을 알아야 하는 경우가 거기에 해당됩니다.

대표적인 경우는 템플릿과 inline 함수입니다.

extern int core(int x);
 
int foo() {
  auto i = core(210);
  return i + 3;
}

위의 코드에서 컴파일러는 core가 int를 리턴하고 int를 받는 함수라는 것만 알아도 foo를 컴파일하는 데는 문제가 없습니다. 하지만 아래는 얘기가 다릅니다.

template<typename T>
int core(T x);
 
int foo() {
  auto i = core<int>(210);
  return i + 3;
}

템플릿의 경우 사용자가 그 함수, core를 완전히 만드는 게 아니라 컴파일러에게 컴파일 직전 그 함수를 어떻게 만들지 알려줄 뿐입니다. 컴파일러는 core(210)를 보는 순간, 템플릿 정의를 보고 core라는 이름의, int를 받아 int를 리턴하는 함수를 그때 생성하게 됩니다. 그래서 이 템플릿 정의는 foo를 컴파일할 때 반드시 보여야 합니다.

인라인도 마찬가집니다. 인라인은 컴파일러가 함수 호출 자리에 호출되는 함수의 내용을 갈아 끼워넣는 것인데 함수의 내용을 모른다면 할 수가 없죠.

정의를 헤더에 다 집어넣는 건 일반적으로는 좋은 습관 같진 않습니다. inline이나 template이 아니라면요.

익명 사용자의 이미지

Quote:
C/C++에서는 함수의 선언과 정의를 .cpp, .h에 구분해 놓는다고 배웠습니다.

C++언어에서 위 가이드라인은 매우 많이 간략화된 것입니다.
정확한 규칙을 확인하기 위해서는 One-Definition Rule (ODR)을 확인해야 합니다.

https://en.cppreference.com/w/cpp/language/definition

일반적으로 class definition은, 그 클래스를 "사용"하는 거의 모든 코드에서 필요한 경우가 많습니다. 이는 현실적인 이유 때문인데, definition이 없으면 해당 class instance의 크기조차 알 수 없기 때문이지요. C++ 문법에서 이는 "incompletely-defined object type"으로 나타나며, 그 사용처가 상당히 제한됩니다.

이러한 이유로 C++에서는 클래스의 정의가 여러 해석 단위에 나타나는 것을 허용합니다. 다만, 동일한 클래스의 정의는 "동일"해야 합니다. (디테일은 생략합니다.) 현실적으로 이는 클래스 정의를 헤더 파일에 두어 여러 소스 파일에서 include하는 형태로 구현됩니다.

경우에 따라서, private field 및 method까지 고스란히 헤더 파일에 올라가 모든 소스 파일들이 의존하게 된다는 점이 별로 달갑지 않은 경우가 있는데, 그런 경우 PImpl 같은 패턴을 쓰시면 됩니다.

https://en.cppreference.com/w/cpp/language/pimpl

awidesky의 이미지

우선 답변 정말 감사드립니다.
구글에서 각종 자료를 찾아봤는데, 마지막까지 조금 햇갈리는 부분이 있네요;;
만약 각 translation unit (간단한 경우 cpp파일)마다 같은 이름을 가진 클래스가 동일한 정의로 존재한다면, 링커나 컴파일러가 이러한 중복을 없애 주나요?
아니면 모든 정의가 identical하다고 생각하고 실행파일의 크기가 그만큼 늘어나게 되나요?

익명 사용자의 이미지

감사 인사를 하려거든 답변 받은 직후에 해야지,
1년쯤 뒤에 추가 질문을 달면서 하면, 답변자가 어떤 생각을 하겠습니까.

뭐 새삼 감사 인사 받자고 답변 다는 것은 아니지만 괜히 빈정 상하네요.
다른 답변자를 알아보시는 게 좋겠습니다.

덧. "구글에서 각종 자료를 찾아봤는데" 라고 할 때는 어떤 자료를 찾아봤는지도 달아주세요.
안 그러면 무성의한 면피성 커멘트로밖에 안 보입니다.

======

농담입니다. 반쯤은 진담이지만요.

아무튼, 이것저것 알아보실 필요 없이 앞서 달아드린 링크에도 이미 답이 있어요.

https://en.cppreference.com/w/cpp/language/definition

Quote:
There can be more than one definition in a program of each of the following: class type, (후략)

If all these requirements are satisfied, the program behaves as if there is only one definition in the entire program. Otherwise, the program is ill-formed, no diagnostic required.

보이십니까?

말씀하신 경우엔, 마치 프로그램 전체에 단 하나의 정의만 있는 것처럼 프로그램이 동작한다는 겁니다.

"처럼 (as if)"이 중요합니다. 원래 C++언어 표준이 그래요.
설령 실제로는 그렇지 않다고 하더라도, 그런 것처럼 동작하기만 하면 아무래도 상관없다는 얘기죠.

======

구현 측면에서 보면, 링커가 중복된 정의를 제거하여 단 한 벌만 남겨놓을 거라고 기대하는 것이 합리적입니다.

그게 그렇게 어렵지도 않은데 공연히 바이나리의 크기를 키울 이유가 없으니까요.

하지만 언어 표준 측면에서 그걸 보장하지는 않고, 제가 봤을 땐 컴파일러 internal을 직접 깊게 파 보지 않는 한 이 부분을 커버하는 다른 매뉴얼이 있을 것 같지는 않아 보이는군요.

awidesky의 이미지

젤 처음 질문을 달았을 때에는 고3 때라 선배님들 답변이 잘 이해되지 않았었고, 대입에 치여 나중에 봐야지 나중에 봐야지 하다 보니 이렇게 되었습니다^^;;
답변이 처음 달렸을 때에는 아무 언급 없다가 한참 뒤에 추가질문 달린 것 보면 제가 봐도 기분이 나쁘셨을 만 합니다. 사과 말씀 드립니다..

https://stackoverflow.com/questions/1809679/difference-between-implementing-a-class-inside-a-h-file-or-in-a-cpp-file
이전에 관련해서 찾아본 것 중에 요 링크에서(혹시 나중에 구글을 통해 이 질문에 들어오실지도 모르는 분을 위해..) inline키워드에 대한 정의 개념이 뿌리째 흔들리고 혼란이 오더군요;;
같은 함수의 definition을 여러개 허용한다면 각 심볼을 어떻게 구분하는가?(<-최적화 레벨에 따라 달라지더군요..? ㅎㄷㄷ) 에서부터 시작해서 옆길로 여러번 빠졌습니다. "세세한 구현보다는 약속에 집중하는 것이 C++의 표준이다"는 말씀 들으니 방향성이 이제 잡히는 것 같습니다.

https://www.reddit.com/r/cpp_questions/comments/nrwcnl/onedefinitionrule_is_everything_in_header_ok/h0o8yqc/?context=3
낮은 실력으로 stack overflow에 허접하게 답변 달았다가 downvote 받을까 봐(^^;;) 레딧에 질문을 올려봤었는데, 아무래도 직접 컴파일을 해보고 비교하는 게 좋은 듯 하다는 느낌을 받았었습니다.
(프로그램이 어떻게 "behave"하는지, 링커가 어떻게 돌아가는지에 대해서는 찾을 수 있었지만, 컴파일된 바이너리에서 여러 정의가 아예 빠지는지 아니면 translation unit이 달라서 남겨놓는지가 조금 햇갈렸던 것 같습니다
사실 다른 자료에서도(so였는데 링크는 기억이 안 나네요) 메모리도 기가바이트 시대에 함수 definition 몇개 더 추가된다고 무슨 일이 있겠으며, 바이너리 파일 크기가 문제가 될 정도로 커지면 알아서 size관련 최적화가 이루어지니 걱정 마라는 글이 있긴 했지만, 아무래도 궁금해서 실례 무릅쓰고 질문하게 되었습니다 ㅎㅎ;;

jick의 이미지

> 감사 인사를 하려거든 답변 받은 직후에 해야지,
> 1년쯤 뒤에 추가 질문을 달면서 하면, 답변자가 어떤 생각을 하겠습니까.
> 뭐 새삼 감사 인사 받자고 답변 다는 것은 아니지만 괜히 빈정 상하네요.
> 다른 답변자를 알아보시는 게 좋겠습니다.

농담이라도 이런 말은 안 쓰시는 게 낫지 않나 싶네요.

댓글 달기

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