3 回答

TA貢獻1827條經(jīng)驗 獲得超4個贊
不需要將賦值運算符設(shè)為虛擬。
下面的討論是關(guān)于的operator=,但它也適用于接受了所討論類型的任何運算符重載,以及接受了所討論類型的任何函數(shù)。
下面的討論表明,在尋找匹配的函數(shù)簽名方面,虛擬關(guān)鍵字不知道參數(shù)的繼承。在最后一個示例中,它顯示了在處理繼承類型時如何正確處理分配。
虛函數(shù)不知道參數(shù)的繼承:
要使虛擬功能發(fā)揮作用,功能的簽名必須相同。因此,即使在以下示例中,將operator =設(shè)為虛擬,該調(diào)用也永遠不會充當D中的虛擬函數(shù),因為operator =的參數(shù)和返回值不同。
該功能B::operator=(const B& right)與D::operator=(const D& right)100%完全不同,被視為2個不同的功能。
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
class D : public B
{
public:
virtual D& operator=(const D& right)
{
x = right.x;
y = right.y;
return *this;
}
int y;
};
默認值,并具有2個重載運算符:
盡管可以定義一個虛函數(shù),以便在將D分配給類型B的變量時為D設(shè)置默認值。即使您的B變量確實是存儲在B的引用中的D,也不會。D::operator=(const D& right)功能。
在以下情況下,將使用存儲在2個B引用中的2個D對象的賦值...使用D::operator=(const B& right)覆蓋。
//Use same B as above
class D : public B
{
public:
virtual D& operator=(const D& right)
{
x = right.x;
y = right.y;
return *this;
}
virtual B& operator=(const B& right)
{
x = right.x;
y = 13;//Default value
return *this;
}
int y;
};
int main(int argc, char **argv)
{
D d1;
B &b1 = d1;
d1.x = 99;
d1.y = 100;
printf("d1.x d1.y %i %i\n", d1.x, d1.y);
D d2;
B &b2 = d2;
b2 = b1;
printf("d2.x d2.y %i %i\n", d2.x, d2.y);
return 0;
}
印刷品:
d1.x d1.y 99 100
d2.x d2.y 99 13
這表明D::operator=(const D& right)從未使用過。
如果沒有啟用virtual關(guān)鍵字,B::operator=(const B& right)您將獲得與上述相同的結(jié)果,但是y的值將不會初始化。即它將使用B::operator=(const B& right)
RTTI將這一切捆綁在一起的最后一步:
您可以使用RTTI正確處理傳入類型的虛函數(shù)。這是弄清楚如何處理可能繼承的類型時如何正確處理分配的難題的最后一部分。
virtual B& operator=(const B& right)
{
const D *pD = dynamic_cast<const D*>(&right);
if(pD)
{
x = pD->x;
y = pD->y;
}
else
{
x = right.x;
y = 13;//default value
}
return *this;
}

TA貢獻2012條經(jīng)驗 獲得超12個贊
RTTI將這一切捆綁在一起的最后一步:
您可以使用RTTI正確處理傳入類型的虛函數(shù)。這是弄清楚如何處理可能繼承的類型時如何正確處理分配的難題的最后一部分。
virtual B& operator=(const B& right)
{
const D *pD = dynamic_cast<const D*>(&right);
if(pD)
{
x = pD->x;
y = pD->y;
}
else
{
x = right.x;
y = 13;//default value
}
return *this;
}
我想在此解決方案中添加一些說明。讓賦值運算符聲明與上述相同有三個問題。
編譯器生成一個賦值運算符,該賦值運算符帶有一個const D&參數(shù),該參數(shù)不是虛擬的,并且沒有執(zhí)行您可能認為的操作。
第二個問題是返回類型,您正在返回對派生實例的基本引用。無論如何,代碼可能不會有太大的問題。還是最好相應(yīng)地返回引用。
第三個問題,派生類型賦值運算符不調(diào)用基類賦值運算符(如果要復(fù)制私有字段,該怎么辦?),將賦值運算符聲明為virtual不會使編譯器為您生成一個。這是沒有賦值運算符的至少兩個重載來獲得所需結(jié)果的副作用。
考慮基類(與我引用的帖子中的基類相同):
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
以下代碼完善了我引用的RTTI解決方案:
class D : public B{
public:
// The virtual keyword is optional here because this
// method has already been declared virtual in B class
/* virtual */ const D& operator =(const B& b){
// Copy fields for base class
B::operator =(b);
try{
const D& d = dynamic_cast<const D&>(b);
// Copy D fields
y = d.y;
}
catch (std::bad_cast){
// Set default values or do nothing
}
return *this;
}
// Overload the assignment operator
// It is required to have the virtual keyword because
// you are defining a new method. Even if other methods
// with the same name are declared virtual it doesn't
// make this one virtual.
virtual const D& operator =(const D& d){
// Copy fields from B
B::operator =(d);
// Copy D fields
y = d.y;
return *this;
}
int y;
};
這似乎是一個完整的解決方案,但事實并非如此。這不是一個完整的解決方案,因為當您從D派生時,您將需要1個運算符=接受const B&,1個運算符=接受const D&,以及一個需要const D2&的運營商。結(jié)論是顯而易見的,運算符=()重載的數(shù)量等于超類的數(shù)量+ 1。
考慮到D2繼承了D,讓我們看一下兩個繼承的operator =()方法的外觀。
class D2 : public D{
/* virtual */ const D2& operator =(const B& b){
D::operator =(b); // Maybe it's a D instance referenced by a B reference.
try{
const D2& d2 = dynamic_cast<const D2&>(b);
// Copy D2 stuff
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
/* virtual */ const D2& operator =(const D& d){
D::operator =(d);
try{
const D2& d2 = dynamic_cast<const D2&>(d);
// Copy D2 stuff
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
};
顯然,運算符=(const D2&)僅復(fù)制字段,想象一下它在那里。我們可以注意到繼承的運算符=()重載中的模式。遺憾的是,我們無法定義可以處理這種模式的虛擬模板方法,我們需要多次復(fù)制和粘貼相同的代碼才能獲得完整的多態(tài)賦值運算符,這是我所看到的唯一解決方案。也適用于其他二進制運算符。
編輯
如評論中所述,使生活變得更輕松的最少方法是定義最頂層的超類賦值運算符=(),然后從所有其他超類運算符=()方法中調(diào)用它。同樣,在復(fù)制字段時,可以定義_copy方法。
class B{
public:
// _copy() not required for base class
virtual const B& operator =(const B& b){
x = b.x;
return *this;
}
int x;
};
// Copy method usage
class D1 : public B{
private:
void _copy(const D1& d1){
y = d1.y;
}
public:
/* virtual */ const D1& operator =(const B& b){
B::operator =(b);
try{
_copy(dynamic_cast<const D1&>(b));
}
catch (std::bad_cast){
// Set defaults or do nothing.
}
return *this;
}
virtual const D1& operator =(const D1& d1){
B::operator =(d1);
_copy(d1);
return *this;
}
int y;
};
class D2 : public D1{
private:
void _copy(const D2& d2){
z = d2.z;
}
public:
// Top-most superclass operator = definition
/* virtual */ const D2& operator =(const B& b){
D1::operator =(b);
try{
_copy(dynamic_cast<const D2&>(b));
}
catch (std::bad_cast){
// Set defaults or do nothing
}
return *this;
}
// Same body for other superclass arguments
/* virtual */ const D2& operator =(const D1& d1){
// Conversion to superclass reference
// should not throw exception.
// Call base operator() overload.
return D2::operator =(dynamic_cast<const B&>(d1));
}
// The current class operator =()
virtual const D2& operator =(const D2& d2){
D1::operator =(d2);
_copy(d2);
return *this;
}
int z;
};
不需要設(shè)置默認值的方法,因為它只會收到一個調(diào)用(在基本運算符=()重載中)。在一個地方完成復(fù)制字段時的更改,并且所有operator =()重載都將受到影響并具有其預(yù)期的目的。
- 3 回答
- 0 關(guān)注
- 698 瀏覽
添加回答
舉報