C++中的赋值操作符可以被定义为虚函数。为什么需要这样做?其他运算符能否也变成虚函数?
C++中的赋值操作符可以被定义为虚函数。为什么需要这样做?其他运算符能否也变成虚函数?
operator=
的,但它也适用于任何接受相应类型参数的重载操作符以及任何接受相应类型参数的函数。B::operator=(const B& right)
和 D::operator=(const D& right)
是完全不同的两个函数,它们被视为两个不同的函数。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;
};
默认值和两个重载运算符:
你可以定义一个虚函数来设置在变量类型为B的情况下D的默认值,即使你的B变量实际上是一个存储在B引用中的D。在这种情况下,你将不会得到 D::operator=(const D& right)
函数。
在以下情况下,当两个D对象存储在两个B引用中时,将使用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)
从未被使用。
如果在B::operator=(const B& right)
上没有virtual关键字,您将得到与上面相同的结果,但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;
}
这取决于运算符。
使赋值运算符虚拟的要点是允许您获得覆盖它以复制更多字段的好处。
因此,如果您有一个Base&并且实际上具有Derived&作为动态类型,并且Derived具有更多字段,则将正确地复制这些字段。
但是,存在风险,即LHS是Derived,而RHS是Base,因此当在Derived中运行虚拟运算符时,您的参数不是Derived并且无法从中获取字段。
这里有一个很好的讨论:http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html
我想对这个解决方案进行一些补充说明。将赋值运算符声明为与上述相同会出现三个问题。Brian R. Bondy wrote:
One last step to tie it all together, RTTI:
You can use RTTI to properly handle virtual functions that take in your type. Here is the last piece of the puzzle to figure out how to properly handle assignment when dealing with possibly inherited types.
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; }
class B
{
public:
virtual B& operator=(const B& right)
{
x = right.x;
return *this;
}
int x;
};
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个operator=,它需要const B&,1个operator=,它需要const D&和一个operator,它需要const D2&。显然,operator=()重载的数量等于超类的数量加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;
}
};
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 =()重载中)。复制字段时的更改在一个地方完成,所有operator =()重载都受到影响并承载其预期目的。
感谢sehe的建议。
D& operator=(D const&) = delete;
。如果你必须使它可复制赋值,那么至少将实现中继到基本情况的虚拟方法。很快这就成为了Cloneable模式的候选,所以你可以使用私有虚拟函数,如GotW18,同时也更不容易混淆。换句话说,多态类与值语义不相容。永远不会。代码表明隐藏是困难的。完全取决于开发人员... - sehe虚拟赋值在以下场景中使用:
//code snippet
Class Base;
Class Child :public Base;
Child obj1 , obj2;
Base *ptr1 , *ptr2;
ptr1= &obj1;
ptr2= &obj2 ;
//Virtual Function prototypes:
Base& operator=(const Base& obj);
Child& operator=(const Child& obj);
情况1: obj1 = obj2;
在这种情况下,虚拟概念不起任何作用,因为我们在Child
类上调用了operator=
。
情况2和3:*ptr1 = obj2;
*ptr1 = *ptr2;
这里的赋值不会按预期进行。原因是调用了Base
类上的operator=
。
可以通过以下方式纠正:
1)转换类型
dynamic_cast<Child&>(*ptr1) = obj2; // *(dynamic_cast<Child*>(ptr1))=obj2;`
dynamic_cast<Child&>(*ptr1) = dynamic_cast<Child&>(*ptr2)`
2) 虚拟概念
现在仅仅使用virtual Base& operator=(const Base& obj)
是不够的,因为Child
和Base
的operator=
签名不同。
我们需要在Child类中添加Base& operator=(const Base& obj)
以及它通常的Child& operator=(const Child& obj)
定义。重要的是包含后面的定义,因为在缺少它的情况下将调用默认分配运算符。(obj1=obj2
可能不会产生预期的结果)
Base& operator=(const Base& obj)
{
return operator=(dynamic_cast<Child&>(const_cast<Base&>(obj)));
}
情况4:obj1 = *ptr2;
在这种情况下,编译器会在Child
中查找operator=(Base& obj)
的定义,因为operator=
是在Child
上调用的。但由于其不存在且Base
类型不能隐式转换为child
,所以会抛出错误。(需要像obj1=dynamic_cast<Child&>(*ptr1);
一样进行强制转换)
如果按照情况2&3实现,则可以处理此场景。
正如可以看到的那样,在使用Base类指针/引用进行赋值时,虚拟赋值使调用更加优雅。
我们还可以将其他运算符也设置为虚拟的吗?可以
dynamic_cast<const Child &>(obj)
而不是dynamic_cast<Child&>(const_cast<Base&>(obj))
会更有意义,不是吗? - Nemoshort
到int
...)的。 - curiousguy只有在您想要保证从您的类派生的类正确复制其所有成员时才需要使用。如果您没有使用多态性,那么您不需要担心这个问题。
我不知道有什么会阻止您将任何运算符虚拟化 - 它们只是特殊情况下的方法调用。
此页面提供了关于所有这些工作原理的优秀和详细的描述。