虚拟赋值运算符C ++
C ++中的赋值运算符可以是虚拟的。 为什么需要? 我们能否让其他运营商也变得虚拟?
赋值运算符不一定是虚拟的。
下面的讨论是关于operator=
,但是它也适用于任何涉及所讨论types的操作符重载,以及涉及所讨论types的任何函数。
下面的讨论表明virtual关键字不知道参数的inheritance关于find一个匹配的函数签名。 在最后的例子中,它展示了如何在处理inheritancetypes时正确处理赋值。
虚函数不知道参数的inheritance:
一个函数的签名需要与虚拟相同。 所以即使在下面的例子中知道,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的默认值,当它被分配给Btypes的variables时。即使你的Bvariables实际上是一个存储在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)
从不使用。
如果没有B::operator=(const B& right)
上的virtual关键字,你将得到和上面相同的结果,但是y的值不会被初始化。 即它会使用B::operator=(const B& right)
最后一步,RTTI:
您可以使用RTTI来正确处理采用您的types的虚拟function。 在处理可能的inheritancetypes时,如何正确处理赋值,这是最后一块难题。
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&作为一个dynamictypes,Derived有更多的字段,正确的东西被复制。
然而,那么你的LHS是一个派生的风险,而RHS是一个基地,所以当虚拟运营商运行在派生你的参数不是一个派生,你没有办法让它的领域。
这是一个很好的讨论: http : //icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html
Brian R. Bondy写道:
最后一步,RTTI:
您可以使用RTTI来正确处理采用您的types的虚拟function。 在处理可能的inheritancetypes时,如何正确处理赋值,这是最后一块难题。
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; }
我想在这个解决scheme中增加一些评论。 赋值运算符与上面相同有三个问题。
编译器会生成一个赋值运算符,该运算符需要一个不是虚拟的const D&参数,并且不会执行您可能认为的操作。
第二个问题是返回types,您正在返回对派生实例的基本引用。 无论如何,代码可能不是一个问题。 不过最好还是相应地返回引用。
第三个问题,派生types赋值操作符不会调用基类赋值操作符(如果存在您想要复制的私有字段,那么将赋值操作符声明为虚函数将不会使编译器为您生成一个)。 这是一个副作用,没有至less两个赋值运算符的重载获得想要的结果。
考虑到基类(与我引用的post相同):
class B { public: virtual B& operator=(const B& right) { x = right.x; return *this; } int x; };
以下代码完成了我所引用的RTTI解决scheme:
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 = dy; } 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 = dy; return *this; } int y; };
这似乎是一个完整的解决scheme,但事实并非如此。 这不是一个完整的解决scheme,因为当从D派生时,需要1个运算符=需要const B& ,1个运算符=需要const D&以及一个运算符需要const D2& 。 结论是显而易见的,operator =()重载的次数与超类+ 1的次数是等价的。
考虑到D2inheritance了D,我们来看看两个inheritance的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; } };
很显然, operator =(const D2&)只是复制字段,想象它就在那里。 我们可以注意到inheritance的运算符=()重载模式。 可悲的是,我们不能定义虚拟模板方法来处理这种模式,我们需要复制和粘贴多次相同的代码,以得到一个完整的多态赋值运算符,我看到的唯一的解决scheme。 也适用于其他二元运算符。
编辑
正如在注释中提到的那样,为了简化生活,最简单的方法是定义最顶层的超类赋值operator =(),并从所有其他的超类operator =()方法中调用它。 另外,当复制字段时,可以定义一个_copy方法。
class B{ public: // _copy() not required for base class virtual const B& operator =(const B& b){ x = bx; 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的build议。
在以下情况下使用虚拟分配:
//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;
在这个虚拟的概念不扮演任何angular色,因为我们调用operator=
Child
类。
情况2&3:* ptr1 = obj2;
* ptr1 = * ptr2;
这里的分配不会像预期的那样。 正在operator=
原因是在Base
类上调用。
它可以通过以下任一方式进行纠正:
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)
定义。 它的重要性在于包含后面的定义,因为缺less该缺省赋值操作符将被调用( 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
types不能被隐式提升为child
,所以会通过错误(需要像obj1=dynamic_cast<Child&>(*ptr1);
)。
如果我们按照案例2和3来实施,这个场景将被照顾。
由于可以看到虚拟赋值使得在使用基类指针/引用赋值的情况下调用更优雅。
我们能否让其他运营商也变得虚拟? 是
只有当你想保证从你的类派生的类能够正确地复制它们的所有成员时才需要它。 如果你没有做多态的事情,那么你并不需要担心这一点。
我不知道任何会阻止你虚拟化任何你想要的操作符的东西 – 它们只不过是特殊的方法调用而已。
这个页面提供了一个很好的和详细的说明如何所有这些工作。
运算符是一种特殊语法的方法。 你可以像对待任何其他方法一样对待它…