[英]Inheritance and polymorphism using abstract class
我有一個抽象類型A
,以及兩個派生類型A1
和A2
。
我想在類A中添加一個方法M,它采用類型A的參數。但是,我需要ad hoc多態。
實際上,我需要3個實現: A1::M(A1 a)
, A1::M(A2 a)
和A2::(A1 a)
, A2::M(A2 a)
。 但是我想用一種抽象的方法用類型A的指針調用方法M.
我可以將所有簽名聲明放在A
類中,但它很糟糕。
使用模擬雙重調度 。
class A {
public:
virtual void M(A &) = 0;
virtual void M(A1 &) = 0;
virtual void M(A2 &) = 0;
};
class A1 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A1\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A1\n"; }
};
class A2 : public A {
public:
virtual void M(A &a) { a.M(*this); }
virtual void M(A1 &a) { std::cout << "A1 <- A2\n"; }
virtual void M(A2 &a) { std::cout << "A2 <- A2\n"; }
};
(參見http://ideone.com/nycls 。)
我不認為有一種方法可以避免基類中的多次重載。
如果你想要多態行為 - 那么你需要的只是基類A中的一個方法。然后你可以在A1,A2中重新實現該方法。
之后你可以寫:
A *a1 = new A1();
A *a2 = new A2();
a1->M(a2); //polymorphic behavior
如果你做這樣的事情:
struct A
{
virtual void M(A *a) {}
};
struct A1 : public A
{
virtual void M(A1 *a) {cout << "A1" << endl;}
virtual void M(A *a) {cout << "A" << endl;}
};
然后:
A1 * a1 = new A1();
a1->M(a1); //prints "A1"
A * a = a1;
a->M(a1); //prints "A"
我不認為這是你想要的行為
為什么不做那樣的事情呢?
void A1::M( A a )
{
if( dynamic_cast< A1* >( &a ) )
{
// do your A1::M1(A1 a) stuff
}
else
if( dynamic_cast< A2* >( &a ) )
{
// do your A1::M2(A2 a) stuff
}
else
{
throw std::logic_error( "Unsupported A type." );
}
}
為A2 :: M等效嗎?
這是雙重調度。 當你寫:
A* p1;
A* p2;
p1->M(*p2);
應該在*p1
的類型和*p2
的類型上發送。
在開始之前,您必須意識到這意味着n^2
函數用於n
種不同的派生類型。 在某個地方,某人必須知道所有派生類型(除非您可以為未知的一對類型定義某種“默認”實現)。
有兩種方法可以實現這一點。 最簡單的,如果層次結構是關閉的(即客戶端代碼不能引入新的派生類)確實在基類中使用了大量的虛函數 - 通常是受保護的,因為它們不是設計為在層次結構之外調用的:
// Forward references needed for all derived classes...
class A1;
class A2;
// ...
class A
{
protectd:
virtual void doM(A1* arg) = 0;
virtual void doM(A2* arg) = 0;
// ...
public:
virtual void M(A& arg) = 0;
};
在派生類中, M
的實現始終是相同的:
void A1::M(A& arg)
{
arg.doM( this );
}
這很簡單,而且效率相對較高,但每次添加新的派生類時,都需要對抽象基類和所有派生類(必須實現新的虛函數)進行更改。 但是,它對於封閉的層次結構非常有用; 我已經在類中使用了它的部分行為的策略模式,其中各種策略都在源文件中定義,而不是暴露給客戶端(策略的抽象基礎只在標題中向前聲明) ,所以如果我添加策略,則不需要更改標頭)。
更通用的解決方案是涉及std::map
,其中一對typeid
作為索引。 您不能直接使用typeid
,因為它不可復制。 C ++ 11提供了一個type_index
來包裝它; 如果你使用的是較舊的編譯器,那么自己實現一個編譯器是相當簡單的。 基本原則是(可能在A本身):
typedef std::pair<std::type_index, std::type_index> TypePairKey;
typedef void (*FuncPtr)( M* arg1, M* arg2 );
typedef std::unordered_map<TypePairKey, FuncPtr> DispatchMap;
static DispatchMap ourDispatchMap;
有:
void M( A& arg ) // NOT virtual !!!
{
DispatchMap::iterator entry
= ourDispatchMap.find(
DispatchMap::value_type( typeid( *this ), typeid( arg ) ) );
assert( entry != ourDispatchMap.end() );
// Or some default handling, maybe throw NotYetImplemented()
(*entry->second)( this, &arg );
}
真正的問題在於編寫每個單獨的函數並在地圖中插入它們的地址(在第一次使用之前)。 of them. 當然,函數本身可以使用dynamic_cast
,甚至是static_cast
,如果你可以確定它們只能從這里調用,並且它們可以是所涉及的類的朋友,但是仍然有個。 (一種常見的解決方案是使它們成為其中一個類的靜態成員,並使每個派生類定義一個類型的靜態成員,該成員執行其負責的所有函數的注冊。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.