3 回答
TA贡献1827条经验 获得超4个赞
不需要将赋值运算符设为虚拟。
下面的讨论是关于的operator=,但它也适用于接受了所讨论类型的任何运算符重载,以及接受了所讨论类型的任何函数。
下面的讨论表明,在寻找匹配的函数签名方面,虚拟关键字不知道参数的继承。在最后一个示例中,它显示了在处理继承类型时如何正确处理分配。
虚函数不知道参数的继承:
要使虚拟功能发挥作用,功能的签名必须相同。因此,即使在以下示例中,将operator =设为虚拟,该调用也永远不会充当D中的虚拟函数,因为operator =的参数和返回值不同。
该功能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个重载运算符:
尽管可以定义一个虚函数,以便在将D分配给类型B的变量时为D设置默认值。即使您的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关键字,B::operator=(const B& right)您将获得与上述相同的结果,但是y的值将不会初始化。即它将使用B::operator=(const B& right)
RTTI将这一切捆绑在一起的最后一步:
您可以使用RTTI正确处理传入类型的虚函数。这是弄清楚如何处理可能继承的类型时如何正确处理分配的难题的最后一部分。
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条经验 获得超12个赞
RTTI将这一切捆绑在一起的最后一步:
您可以使用RTTI正确处理传入类型的虚函数。这是弄清楚如何处理可能继承的类型时如何正确处理分配的难题的最后一部分。
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&参数,该参数不是虚拟的,并且没有执行您可能认为的操作。
第二个问题是返回类型,您正在返回对派生实例的基本引用。无论如何,代码可能不会有太大的问题。还是最好相应地返回引用。
第三个问题,派生类型赋值运算符不调用基类赋值运算符(如果要复制私有字段,该怎么办?),将赋值运算符声明为virtual不会使编译器为您生成一个。这是没有赋值运算符的至少两个重载来获得所需结果的副作用。
考虑基类(与我引用的帖子中的基类相同):
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&的运营商。结论是显而易见的,运算符=()重载的数量等于超类的数量+ 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&)仅复制字段,想象一下它在那里。我们可以注意到继承的运算符=()重载中的模式。遗憾的是,我们无法定义可以处理这种模式的虚拟模板方法,我们需要多次复制和粘贴相同的代码才能获得完整的多态赋值运算符,这是我所看到的唯一解决方案。也适用于其他二进制运算符。
编辑
如评论中所述,使生活变得更轻松的最少方法是定义最顶层的超类赋值运算符=(),然后从所有其他超类运算符=()方法中调用它。同样,在复制字段时,可以定义_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;
};
不需要设置默认值的方法,因为它只会收到一个调用(在基本运算符=()重载中)。在一个地方完成复制字段时的更改,并且所有operator =()重载都将受到影响并具有其预期的目的。
- 3 回答
- 0 关注
- 666 浏览
添加回答
举报