아무래도 컴파일러 버그인 듯한 문제를 발견했는데, 단순히 내 자신이 무지한 탓인가 싶어 블로그에 올려봅니다. 소스 코드는 Visual Studio 2008로 작성해서 올려놨습니다.
// LibraryA.cpp : library file. #include "stdafx.h" #include <iostream> using namespace System; template<typename ABC> public ref class ManagedClassA { public: int V; ManagedClassA(int v) { V = v; } static bool operator == (ManagedClassA^ a, ManagedClassA^ b) { // Return true if the fields match: return ( (a->V + b->V) % 2 == 0 ); } static bool operator != (ManagedClassA^ a, ManagedClassA^ b) { return !(a == b); } }; public ref class ManagedClassB : public ManagedClassA<int> { public: ManagedClassB(int v) : ManagedClassA(v) { } }; // 단순 테스트용 클래스 public ref class Tester { public: static void Test1() { ManagedClassB^ b1 = gcnew ManagedClassB(1); ManagedClassB^ b2 = gcnew ManagedClassB(3); if(b1 == b2) System::Console::WriteLine("성공"); else System::Console::WriteLine("실패"); } }; template<typename ABC> public ref class ManagedClassC { public: int V; ManagedClassC(int v) { V = v; } static bool operator == (ManagedClassC^ a, ManagedClassC^ b) { // Return true if the fields match: return ( (a->V + b->V) % 2 == 0 ); } static bool operator != (ManagedClassC^ a, ManagedClassC^ b) { return !(a == b); } }; public ref class ManagedClassD : public ManagedClassC<int> { public: ManagedClassD(int v) : ManagedClassC(v) { } };
// LibraryB.cpp : main project file. #include "stdafx.h" using namespace System; void Test2() { ManagedClassD^ b1 = gcnew ManagedClassD(1); ManagedClassD^ b2 = gcnew ManagedClassD(3); if(b1 == b2) System::Console::WriteLine("성공"); else System::Console::WriteLine("실패"); } int main(array<System::String ^> ^args) { Tester::Test1(); Test2(); return 0; }
LibraryA는 dll로 빌드한 닷넷 어셈블리고, LibraryB는 콘솔 애플리케이션입니다. 여기서 주목할 것은 LibraryA의 클래스 A, B와 클래스 C, D입니다. 두 코드는 동일합니다. 클래스 A와 클래스 C, 그리고 클래스 B와 클래스 C는 완전히 똑같습니다. 글쎄요. 차이점이 뭘까요? 단 하나입니다. 클래스 A와 B의 연산자 오버로딩을 테스트하는 코드는 LibraryA에 있지만, 클래스 C와 B를 테스트하는 코드는 LibraryB에 있습니다. 얼핏 생각하기엔 완전히 같은 코드지만 실제로 돌려보면 그렇지가 않습니다. 과연 어떤 결과가 나올까요?
성공
실패
테스트 코드가 LibraryA에 있으면 성공하지만 그렇지 않으면 실패합니다. 이게 도대체 어떻게 된 일일까요? 컴파일된 코드를 까보면 문제가 확실해집니다. Reflector를 이용해 컴파일된 코드를 열어봅니다.
ClassA엔 연산자 오버로딩한 메서드 op_Equality
가 있지만, ClassB엔 없습니다.
자, 이게 도대체 무슨 영문일까요? 컴파일러가 바보인가요? 아니면 제가 당연한 걸 모르는 건가요?
PS. C++/CLI에서의 연산자 오버로딩 패턴에 해결책을 적었습니다.
Author Details
Kubernetes, DevSecOps, AWS, 클라우드 보안, 클라우드 비용관리, SaaS 의 활용과 내재화 등 소프트웨어 개발 전반에 도움이 필요하다면 도움을 요청하세요. 지인이라면 가볍게 도와드리겠습니다. 전문적인 도움이 필요하다면 저의 현업에 방해가 되지 않는 선에서 협의가능합니다.
일단 제가 테스트해본 결과로는, LibraryA내에서 operator==이 호출된 적이 없으면 실패하네요.
Class D에서 별 의미도 없이 operator==를 한번 호시켜 봤더니 성공으로 나왔습니다.
최적화 옵션이 꺼져있는데도 operator==에 대한 호출이 없으면 링크에서 빼버리는 것으로 생각됩니다.
그리고 아마도 operator==에 대한 정의가 template쪽에 있을 경우에 그런 문제가 있는 듯 합니다.
네, 제가 말하는 문제가 바로 그겁니다. 아무리 생각해도 정상적인 행동이 아니고, 컴파일러 문제인 듯 한데 참 곤란하네요.
C++/CLI에서 template 키워드를 쓰면 Native C++과 같이 컴파일 타임 코드 생성을 합니다.
C#에서처럼 쓰실려면 template 키워드 대신 generic키워드를 쓰시면 됩니다.
저도 이것땜에 고생 엄청 했어여ㅠㅠㅠ
generic은 제약이 많아서 여기선 일부러 template 쓴 겁니다. C++/CLI에서 생성한 template 클래스는 C#에 가져가 쓰지 못하지만, C++/CLI 코드 내에서는 강력하기 때문에 자주 사용합니다. generic 이 아닌 template 이라 해서 연산자 오버로딩을 컴파일 안 하는 것도 아니구요.
하지만 말했듯이 template키워드를 쓰면 컴파일 타임 코드 생성을 하기 때문에 빌드하는 cpp파일에서 호출하지 않거나 구체화를 하지 않으면 코드 자체가 생성이 않됩니다.
이 문제는 예전에 해결한 기억이 나는데 역시나 찾아보니 해결법을 적어놨네요. https://andromedarabbit.net/cpp_cli_operator_overloading_patterns/
아, virtual 키워드를 붙이면 함수의 본문이 있어야 하니까 만드는군요.
좋은 정보 감사합니다.