Abstract
在中,我們看到了.NET的Generics的multiple constraints是AND的關係,而非OR的關係,若要讓泛型支援OR的關係該如何做呢?Introduction我希望有一個Generic Handler,能同時支援Interface1和Interface2,UML表示如下ISO C++ 1 /**/ /* 2(C) OOMusou 2007 http://oomusou.cnblogs.com 3 4Filename : Template_SupportMultiInterface.cpp 5Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++ 6Description : Demo how to use template support multiple interface 7Release : 06/16/2007 1.0 8*/ 9 #include < iostream > 10 11 using namespace std; 12 13 class Interface1 { 14public:15 virtual void func1() = 0;16 virtual void func2() = 0;17} ; 18 19 class Interface2 { 20public:21 virtual void func1() = 0;22 virtual void func3() = 0;23} ; 24 25 class Class1 : public Interface1 { 26public:27 void func1() { 28 cout << "Class1's func1" << endl;29 }30 void func2() { 31 cout << "Class1's func2" << endl;32 }33} ; 34 35 class Class2 : public Interface2 { 36public:37 void func1() { 38 cout << "Class2's func1" << endl;39 }40 void func3() { 41 cout << "Class2's func3" << endl;42 }43} ; 44 45 class IGeneric { 46public:47 virtual void func1() = 0;48 virtual void func2() = 0;49 virtual void func3() = 0;50} ; 51 52 template < typename T > 53 class GenericHandler : public IGeneric { 54private:55 T* _aClass;5657public:58 GenericHandler(T* aClass) { 59 _aClass = aClass;60 }6162 void func1() { 63 _aClass->func1();64 }65 66 void func2() { 67 dynamic_cast<Interface1*>(_aClass)->func2();68 }69 70 void func3() { 71 dynamic_cast<Interface2*>(_aClass)->func3();72 }73} ; 74 75 76 int main() { 77 Interface1* obj1 = new Class1;78 Interface2* obj2 = new Class2;79 80 IGeneric* foo = new GenericHandler<Interface1>(obj1);81 foo->func1();82 foo->func2();83 84 foo = new GenericHandler<Interface2>(obj2);85 foo->func1();86 foo->func3();87}
執行結果 Class1's func1Class1's func2Class2's func1Class2's func3
ISO C++的template本來就類似macro,所以code並不讓人訝異,唯一是67行和71行的 dynamic_cast < Interface1 *> (_aClass) -> func2();
dynamic_cast < Interface2 *> (_aClass) -> func3();
是不得已而為之,因為func2本來就是Interface1獨有,而func3也是Interface2所獨有,所以得用casting。C#C#的泛型是用Generics,比較類似polymorphism的加強版,最大的特色就是要靠constraints,也因為如此,所以整個架構做了小小的調整,如同上一篇的技巧,將func1往上提到InterfaceBase,讓constraint為InterfaceBase。 1 /**/ /* 2(C) OOMusou 2007 http://oomusou.cnblogs.com 3 4Filename : Generics_SupportMultiInterface.cs 5Compiler : Visual Studio 2005 / C# 2.0 6Description : Demo how to support multiple interface in Generics 7Release : 06/16/2007 1.0 8*/ 9 using System; 10 11 public interface InterfaceBase { 12 void func1();13} 14 15 public interface Interface1 : InterfaceBase { 16 void func2();17} 18 19 public interface Interface2 : InterfaceBase { 20 void func3();21} 22 23 public class Class1 : Interface1 { 24 public void func1() { 25 Console.WriteLine("Class1's func1");26 }27 28 public void func2() { 29 Console.WriteLine("Class1's func2");30 }31} 32 33 public class Class2 : Interface2 { 34 public void func1() { 35 Console.WriteLine("Class2's func1");36 }3738 public void func3() { 39 Console.WriteLine("Class1's func3");40 }41} 42 43 public interface IGeneric { 44 void func1();45 void func2();46 void func3();47} 48 49 public class GenericHandler < T > : IGeneric where T : InterfaceBase { 50 private T _aClass;5152 public GenericHandler(T aClass) { 53 _aClass = aClass;54 }5556 public void func1() { 57 _aClass.func1();58 }59 60 public void func2() { 61 ((Interface1)_aClass).func2();62 }63 64 public void func3() { 65 ((Interface2)_aClass).func3();66 }67} 68 69 public class main { 70 public static void Main() { 71 Interface1 obj1 = new Class1();72 Interface2 obj2 = new Class2();73 74 IGeneric foo = new GenericHandler<Interface1>(obj1);75 foo.func1();76 foo.func2();7778 foo = new GenericHandler<Interface2>(obj2);79 foo.func1();80 foo.func3();81 }82}
執行結果 Class1's func1Class1's func2Class2's func1Class2's func3
如同ISO C++,61行,65行還是得casting。 ((Interface1)_aClass).func2();
((Interface2)_aClass).func3();
C++/CLI這在C++/CLI就有趣了,因為C++/CLI提供兩種泛型,一種是ISO C++的template,一種是.NET的generics。使用template 1 /**/ /* 2 (C) OOMusou 2006 http://oomusou.cnblogs.com 3 4 Filename : Template_SupportMutipleInterface.cpp 5 Compiler : Visual C++ 8.0 / C++/CLI 6 Description : Demo how to support multiple interface in Generics 7 Release : 06/16/2007 1.0 8 */ 9 #include " stdafx.h " 10 11 using namespace System; 12 13 public interface class Interface1 { 14 void func1();15 void func2();16} ; 17 18 public interface class Interface2 { 19 void func1();20 void func3();21} ; 22 23 public ref class Class1 : public Interface1 { 24public:25 virtual void func1() { 26 Console::WriteLine("Class1's func1");27 }28 29 virtual void func2() { 30 Console::WriteLine("Class1's func2");31 }32} ; 33 34 public ref class Class2 : public Interface2 { 35public:36 virtual void func1() { 37 Console::WriteLine("Class2's func1");38 }39 40 virtual void func3() { 41 Console::WriteLine("Class2's func3");42 }43} ; 44 45 public interface class IGeneric { 46 void func1();47 void func2();48 void func3();49} ; 50 51 template < typename T > 52 public ref class GenericHandler : IGeneric { 53private:54 T^ _aClass;5556public:57 GenericHandler(T^ aClass) { 58 _aClass = aClass;59 }6061 virtual void func1() { 62 _aClass->func1();63 }64 65 virtual void func2() { 66 safe_cast<Interface1^>(_aClass)->func2();67 }68 69 virtual void func3() { 70 safe_cast<Interface2^>(_aClass)->func3();71 }72} ; 73 74 int main() { 75 Interface1^ obj1 = gcnew Class1;76 Interface2^ obj2 = gcnew Class2;77 78 IGeneric^ foo = gcnew GenericHandler<Interface1>(obj1);79 foo->func1();80 foo->func2();81 82 foo = gcnew GenericHandler<Interface2>(obj2);83 foo->func1();84 foo->func3();85}
執行結果 Class1's func1Class1's func2Class2's func1Class2's func3
使用generics
1 /**/ /* 2(C) OOMusou 2006 http://oomusou.cnblogs.com 3 4Filename : Generic_SupportMutipleInterface.cpp 5Compiler : Visual C++ 8.0 / C++/CLI 6Description : Demo how to support multiple interface in Generics 7Release : 06/16/2007 1.0 8*/ 9 #include " stdafx.h " 10 11 using namespace System; 12 13 public interface class InterfaceBase { 14 void func1();15} ; 16 17 public interface class Interface1 : InterfaceBase { 18 void func2();19} ; 20 21 public interface class Interface2 : InterfaceBase { 22 void func3();23} ; 24 25 public ref class Class1 : public Interface1 { 26public:27 virtual void func1() { 28 Console::WriteLine("Class1's func1");29 }30 31 virtual void func2() { 32 Console::WriteLine("Class1's func2");33 }34} ; 35 36 public ref class Class2 : public Interface2 { 37public:38 virtual void func1() { 39 Console::WriteLine("Class2's func1");40 }41 42 virtual void func3() { 43 Console::WriteLine("Class2's func3");44 }45} ; 46 47 public interface class IGeneric { 48 void func1();49 void func2();50 void func3();51} ; 52 53 generic < typename T > 54 where T : InterfaceBase 55 public ref class GenericHandler : IGeneric { 56private:57 T _aClass;5859public:60 GenericHandler(T aClass) { 61 _aClass = aClass;62 }6364 virtual void func1() { 65 _aClass->func1();66 }67 68 virtual void func2() { 69 safe_cast<Interface1^>(_aClass)->func2();70 }71 72 virtual void func3() { 73 safe_cast<Interface2^>(_aClass)->func3();74 }75} ; 76 77 int main() { 78 Interface1^ obj1 = gcnew Class1;79 Interface2^ obj2 = gcnew Class2;80 81 IGeneric^ foo = gcnew GenericHandler<Interface1^>(obj1);82 foo->func1();83 foo->func2();84 85 foo = gcnew GenericHandler<Interface2^>(obj2);86 foo->func1();87 foo->func3();88}
執行結果 Class1's func1Class1's func2Class2's func1Class2's func3
以上做法雖然可行,不過並不滿意,GenericHandler雖然同時支援了func1()、func2()、func3(),但func2()只有在泛型傳入為Interface1時使用才不會出現run-time error,若在傳入Interface2時去invoke了func2(),compiler並不會發現錯誤,要到run-time才發現錯誤。理想上,由於func1()對於Interface1或Interface2都支援,所以無論泛型傳入Interface1或Interface2,Interllisense皆該顯示func1(),但由於func2()只配合Interface1,func3()只配合Interface2,所以foo理想上應該透過一個casting後,才能顯示func2()或func3(),這樣可以避免client誤用而當機。 ISO C++ 1 /**/ /* 2(C) OOMusou 2007 http://oomusou.cnblogs.com 3 4Filename : Template_SupportMultiInterface2.cpp 5Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++ 6Description : Demo how to use template support multiple interface 7Release : 06/18/2007 1.0 8*/ 9 #include < iostream > 10 11 using namespace std; 12 13 class Interface1 { 14public:15 virtual void func1() = 0;16 virtual void func2() = 0;17} ; 18 19 class Interface2 { 20public:21 virtual void func1() = 0;22 virtual void func3() = 0;23} ; 24 25 class Class1 : public Interface1 { 26public:27 void func1() { 28 cout << "Class1's func1" << endl;29 }30 void func2() { 31 cout << "Class1's func2" << endl;32 }33} ; 34 35 class Class2 : public Interface2 { 36public:37 void func1() { 38 cout << "Class2's func1" << endl;39 }40 void func3() { 41 cout << "Class2's func3" << endl;42 }43} ; 44 45 class IGenericBase { 46public:47 virtual void func1() = 0;48} ; 49 50 class IGeneric1 : public IGenericBase { 51public:52 virtual void func2() = 0;53} ; 54 55 class IGeneric2 : public IGenericBase { 56public:57 virtual void func3() = 0;58} ; 59 60 template < typename T > 61 class GenericHandler : public IGenericBase, IGeneric1, IGeneric2 { 62private:63 T* _aClass;6465public:66 GenericHandler(T* aClass) { 67 _aClass = aClass;68 }69 70 void func1() { 71 _aClass->func1();72 }73 74 void func2() { 75 dynamic_cast<Interface1*>(_aClass)->func2();76 }77 78 void func3() { 79 dynamic_cast<Interface2*>(_aClass)->func3();80 }81} ; 82 83 int main() { 84 Interface1* obj1 = new Class1;85 Interface2* obj2 = new Class2;86 87 IGenericBase* foo = new GenericHandler<Interface1>(obj1);88 foo->func1();89 dynamic_cast<IGeneric1*>(++foo)->func2();90 91 foo = new GenericHandler<Interface2>(obj2);92 foo->func1();93 dynamic_cast<IGeneric2*>(++++foo)->func3();94}
執行結果 Class1's func1Class1's func2Class2's func1Class2's func3
由於分成Interface1和Interface2,所以GenericHandler的Interface也分成Generic1和Generic2。因為func1()為IGeneric1和IGeneric2共用,所以向上提升到IGenericBase,如此設計有兩個好處:1.GenericHandler有IGenericBase這個最上層的interface,因此可以配合眾多creational pattern合作。2.要使用func2()時必須明確轉型成IGeneric1,要使用func3()時必須明確轉型成IGeneric2,如此可避免client誤用而導致run-time error。若用ISO C++實做,89行和93行非常tricky。 dynamic_cast < IGeneric1 *> ( ++ foo) -> func2();
dynamic_cast < IGeneric2 *> ( ++++ foo) -> func3();
為什麼要++foo和++++foo呢?因為在87行 IGenericBase * foo = new GenericHandler < Interface1 > (obj1);
foo是一個指向IGenericBase的pointer,若要casting成指向IGeneric1的pointer,其中有offset存在,所以必須++foo,若要指向IGeneric2,其offset是++++foo,詳細原理在Stanley B. Lippman的大作Inside the C++ Object Model有解釋。C# /**/ /* (C) OOMusou 2007 http://oomusou.cnblogs.comFilename : Generics_SupportMultiInterface2.csCompiler : Visual Studio 2005 / C# 2.0Description : Demo how to support multiple interface in GenericsRelease : 06/17/2007 1.0*/ using System; public interface InterfaceBase { void func1();} public interface Interface1 : InterfaceBase { void func2();} public interface Interface2 : InterfaceBase { void func3();} public class Class1 : Interface1 { public void func1() { Console.WriteLine("Class1's func1"); } public void func2() { Console.WriteLine("Class1's func2"); }} public class Class2 : Interface2 { public void func1() { Console.WriteLine("Class2's func1"); } public void func3() { Console.WriteLine("Class2's func3"); }} public interface IGenericBase { void func1();} public interface IGeneric1 : IGenericBase { void func2();} public interface IGeneric2 : IGenericBase { void func3();} public class GenericHandler < T > : IGenericBase, IGeneric1, IGeneric2 where T : InterfaceBase { private T _aClass; public GenericHandler(T aClass) { _aClass = aClass; } public void func1() { _aClass.func1(); } void IGeneric1.func2() { ((Interface1)_aClass).func2(); } void IGeneric2.func3() { ((Interface2)_aClass).func3(); }} public class main { public static void Main() { Interface1 obj1 = new Class1(); Interface2 obj2 = new Class2(); IGenericBase foo = new GenericHandler<Interface1>(obj1); foo.func1(); ((IGeneric1)foo).func2(); foo = new GenericHandler<Interface2>(obj2); foo.func1(); ((IGeneric2)foo).func3(); }}
執行結果 Class1's func1Class1's func2Class2's func1Class2's func3
和ISO C++的想法相同,但C#在casting方面就不需考慮offset的問題,且僅使用了C-style的casting。C++/CLI使用template 1 /**/ /* 2 (C) OOMusou 2006 http://oomusou.cnblogs.com 3 4 Filename : Template_SupportMutipleInterface2.cpp 5 Compiler : Visual C++ 8.0 / C++/CLI 6 Description : Demo how to support multiple interface in Generics 7 Release : 06/18/2007 1.0 8 */ 9 #include " stdafx.h " 10 11 using namespace System; 12 13 public interface class Interface1 { 14 void func1();15 void func2();16} ; 17 18 public interface class Interface2 { 19 void func1();20 void func3();21} ; 22 23 public ref class Class1 : public Interface1 { 24public:25 virtual void func1() { 26 Console::WriteLine("Class1's func1");27 }28 29 virtual void func2() { 30 Console::WriteLine("Class1's func2");31 }32} ; 33 34 public ref class Class2 : public Interface2 { 35public:36 virtual void func1() { 37 Console::WriteLine("Class2's func1");38 }39 40 virtual void func3() { 41 Console::WriteLine("Class2's func3");42 }43} ; 44 45 public interface class IGenericBase { 46 void func1();47} ; 48 49 public interface class IGeneric1 : public IGenericBase { 50 void func2();51} ; 52 53 public interface class IGeneric2 : public IGenericBase { 54 void func3();55} ; 56 57 template < typename T > 58 public ref class GenericHandler : IGenericBase, IGeneric1, IGeneric2 { 59private:60 T^ _aClass;6162public:63 GenericHandler(T^ aClass) { 64 _aClass = aClass;65 }6667 virtual void func1() { 68 _aClass->func1();69 }70 71 virtual void func2() = IGeneric1::func2 { 72 safe_cast<Interface1^>(_aClass)->func2();73 }74 75 virtual void func3() = IGeneric2::func3 { 76 safe_cast<Interface2^>(_aClass)->func3();77 }78} ; 79 80 int main() { 81 Interface1^ obj1 = gcnew Class1;82 Interface2^ obj2 = gcnew Class2;83 84 IGenericBase^ foo = gcnew GenericHandler<Interface1>(obj1);85 foo->func1();86 safe_cast<IGeneric1^>(foo)->func2();87 88 foo = gcnew GenericHandler<Interface2>(obj2);89 foo->func1();90 safe_cast<IGeneric2^>(foo)->func3();91}
執行結果 Class1's func1Class1's func2Class2's func1Class2's func3
想法也和ISO C++和C#相同,不過在語法細節上,C++/CLI在casting和explicit interface implementation上和ISO C++與C#不同。1.casting72行 safe_cast < Interface1 ^> (_aClass) -> func2();
ISO C++在casting上有const_cast,dynamic_cast,reinterpret_cast和static_cast,在C++/CLI仍然可用,除此之外,C++/CLI另外提出了safe_cast,專門應付managed部分。2.explicit interface implementation71行 virtual void func2() = IGeneric1::func2 {
就是C#的 void IGeneric1.func2() {
ISO C++並沒有這樣的語法,這是.NET CLI規格新加上去的。使用generics 1 /**/ /* 2 (C) OOMusou 2006 http://oomusou.cnblogs.com 3 4 Filename : Generic_SupportMutipleInterface2.cpp 5 Compiler : Visual C++ 8.0 / C++/CLI 6 Description : Demo how to support multiple interface in Generics 7 Release : 06/16/2007 1.0 8 */ 9 #include " stdafx.h " 10 11 using namespace System; 12 13 public interface class InterfaceBase { 14 void func1();15} ; 16 17 public interface class Interface1 : InterfaceBase { 18 void func2();19} ; 20 21 public interface class Interface2 : InterfaceBase { 22 void func3();23} ; 24 25 public ref class Class1 : public Interface1 { 26public:27 virtual void func1() { 28 Console::WriteLine("Class1's func1");29 }30 31 virtual void func2() { 32 Console::WriteLine("Class1's func2");33 }34} ; 35 36 public ref class Class2 : public Interface2 { 37public:38 virtual void func1() { 39 Console::WriteLine("Class2's func1");40 }41 42 virtual void func3() { 43 Console::WriteLine("Class2's func3");44 }45} ; 46 47 public interface class IGenericBase { 48 void func1();49} ; 50 51 public interface class IGeneric1 : public IGenericBase { 52 void func2();53} ; 54 55 public interface class IGeneric2 : public IGenericBase { 56 void func3();57} ; 58 59 generic < typename T > 60 where T : InterfaceBase 61 public ref class GenericHandler : IGenericBase, IGeneric1, IGeneric2 { 62private:63 T _aClass;64 65public:66 GenericHandler(T aClass) { 67 _aClass = aClass;68 }6970 virtual void func1() { 71 _aClass->func1();72 }73 74 virtual void func2() = IGeneric1::func2 { 75 safe_cast<Interface1^>(_aClass)->func2();76 }77 78 virtual void func3() = IGeneric2::func3 { 79 safe_cast<Interface2^>(_aClass)->func3();80 }81} ; 82 83 int main() { 84 Interface1^ obj1 = gcnew Class1;85 Interface2^ obj2 = gcnew Class2;86 87 IGenericBase^ foo = gcnew GenericHandler<Interface1^>(obj1);88 foo->func1();89 safe_cast<IGeneric1^>(foo)->func2();90 91 foo = gcnew GenericHandler<Interface2^>(obj2);92 foo->func1();93 safe_cast<IGeneric2^>(foo)->func3();94}
C++/CLI若使用generics寫,其實在此範例看不出template和generics的差異,唯一就是在generics需要constraints。 Conclusion
經過幾天的折騰,總算找出了還算滿意的方式,尤其是ISO C++的offset和C++/CLI的explicit interface implementation讓我印象深刻,另外一個遺憾的是,似乎沒用到什麼Design Pattern,只是憑直覺去思考,若有任何建議都非常歡迎。