템플릿 C ++ 클래스를 작성할 때 일반적으로 세 가지 옵션이 있습니다.
(1) 선언과 정의를 헤더에 넣습니다.
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f()
{
...
}
};
또는
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
template <typename T>
inline void Foo::f()
{
...
}
찬성:
범죄자:
- 인터페이스와 메소드 구현이 혼합되어 있습니다. 이것은 "가독성"입니다. 일부는 이것이 일반적인 .h / .cpp 접근 방식과 다르기 때문에 유지 관리 할 수없는 것으로 판단합니다. 그러나 다른 언어 (예 : C # 및 Java)에서는 이것이 문제가되지 않습니다.
- 높은 재 구축 영향 :
Foo
멤버로 새 클래스를 선언하는 경우을 포함해야합니다 foo.h
. 즉, 구현 구현을 변경하면 Foo::f
헤더 및 소스 파일을 통해 전파됩니다.
재구성의 영향을 자세히 살펴 보겠습니다. 템플릿이 아닌 C ++ 클래스의 경우 .h에 선언을, .cpp에 메서드 정의를 넣습니다. 이 방법으로 메소드의 구현이 변경되면 하나의 .cpp 만 다시 컴파일하면됩니다. .h에 모든 코드가 포함되어 있으면 템플릿 클래스와 다릅니다. 다음 예를 살펴보십시오.
// bar.h
#pragma once
#include "foo.h"
struct Bar
{
void b();
Foo<int> foo;
};
// bar.cpp
#include "bar.h"
void Bar::b()
{
foo.f();
}
// qux.h
#pragma once
#include "bar.h"
struct Qux
{
void q();
Bar bar;
}
// qux.cpp
#include "qux.h"
void Qux::q()
{
bar.b();
}
여기서 유일한 사용법 Foo::f
은 inside bar.cpp
입니다. 당신의 구현을 변경하는 경우에는 Foo::f
, 모두 bar.cpp
와 qux.cpp
필요를 다시 컴파일합니다. 의 Foo::f
일부를 Qux
직접 사용하는 부분이 없더라도 두 파일 모두에서 삶을 구현 Foo::f
합니다. 대규모 프로젝트의 경우 곧 문제가 될 수 있습니다.
(2) 선언은 .h에, 정의는 .tpp에 넣고 .h에 포함하십시오.
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
#include "foo.tpp"
// foo.tpp
#pragma once // not necessary if foo.h is the only one that includes this file
template <typename T>
inline void Foo::f()
{
...
}
찬성:
- 매우 편리한 사용법 (헤더 포함).
- 인터페이스와 메소드 정의는 분리되어 있습니다.
범죄자:
이 솔루션은 선언과 메소드 정의를 .h / .cpp와 같이 두 개의 별도 파일로 분리합니다. 그러나이 방법에는 헤더에 메소드 정의가 직접 포함되므로 (1) 과 동일한 재구성 문제가 있습니다.
(3) .h에 선언을하고 .tpp에 정의를 넣지 만 .h에 .tpp를 포함시키지 마십시오.
// foo.h
#pragma once
template <typename T>
struct Foo
{
void f();
};
// foo.tpp
#pragma once
template <typename T>
void Foo::f()
{
...
}
찬성:
- .h / .cpp 분리와 마찬가지로 재건 영향을 줄입니다.
- 인터페이스와 메소드 정의는 분리되어 있습니다.
범죄자:
- 불편한 사용법 :
Foo
멤버를 클래스에 추가 할 때 헤더 Bar
에 포함해야합니다 foo.h
. 당신이 호출하면 Foo::f
cpp를, 당신은 또한 포함 할 필요가 foo.tpp
있다.
실제로 사용하는 .cpp 파일 만 Foo::f
다시 컴파일 하면되므로이 방법을 사용하면 다시 빌드 영향이 줄어 듭니다 . 그러나 가격이 책정됩니다. 모든 파일에는 포함해야합니다 foo.tpp
. 위의 예를 들어 새로운 접근법을 사용하십시오.
// bar.h
#pragma once
#include "foo.h"
struct Bar
{
void b();
Foo<int> foo;
};
// bar.cpp
#include "bar.h"
#include "foo.tpp"
void Bar::b()
{
foo.f();
}
// qux.h
#pragma once
#include "bar.h"
struct Qux
{
void q();
Bar bar;
}
// qux.cpp
#include "qux.h"
void Qux::q()
{
bar.b();
}
당신이 볼 수 있듯이, 유일한 차이점은 추가가의 포함이다 foo.tpp
에서 bar.cpp
. 이것은 불편하고 클래스 호출에 메소드를 호출하는지 여부에 따라 두 번째 포함을 추가하는 것은 매우 추한 것 같습니다. 그러나 다시 빌드 영향을 줄 bar.cpp
입니다 Foo::f
. 의 구현을 변경 한 경우 에만 다시 컴파일하면됩니다 . 파일을 qux.cpp
다시 컴파일 할 필요가 없습니다.
요약:
라이브러리를 구현하면 일반적으로 재 빌드 영향에 신경 쓸 필요가 없습니다. 라이브러리 사용자는 릴리스를 가져 와서 사용하며 라이브러리 구현은 사용자의 일상 업무에서 변경되지 않습니다. 이 경우, 라이브러리는 접근 방식 (1) 또는 (2) 를 사용할 수 있으며 선택한 것을 선택하는 것은 맛의 문제 일뿐입니다.
그러나 응용 프로그램에서 작업 중이거나 회사의 내부 라이브러리에서 작업중인 경우 코드가 자주 변경됩니다. 따라서 재 구축 영향에주의해야합니다. 개발자가 추가 포함을 수락하게하려면 접근 방식 (3)을 선택 하는 것이 좋습니다.