MvG*_*MvG 6 c++ inheritance reference assignment-operator
我目前正在调查多态类型和赋值操作之间的相互作用.我主要担心的是有人是否可能尝试将基类的值分配给派生类的对象,这会导致问题.
从这个答案中我了解到,基类的赋值运算符总是被派生类的隐含定义的赋值运算符隐藏.因此,对于赋值给简单变量,不正确的类型将导致编译器错误.但是,如果通过引用进行赋值,则情况并非如此:
class A { public: int a; };
class B : public A { public: int b; };
int main() {
A a; a.a = 1;
B b; b.a = 2; b.b = 3;
// b = a; // good: won't compile
A& c = b;
c = a; // bad: inconcistent assignment
return b.a*10 + b.b; // returns 13
}
Run Code Online (Sandbox Code Playgroud)
这种形式的赋值可能会导致不一致的对象状态,但是没有编译器警告,并且代码看起来对我来说是非邪恶的.
是否有任何成熟的习惯用于检测此类问题?
我想我只能希望运行时检测,如果我找到这样一个无效的赋值就抛出异常.我刚才能想到的最好的方法是基类中的用户定义的assigment运算符,它使用运行时类型信息来确保this
实际上是指向base实例的指针,而不是派生类的指针,然后执行手动逐个成员的副本.这听起来像很多开销,严重影响代码可读性.有更容易的事吗?
编辑:由于某些方法的适用性似乎取决于我想做什么,这里有一些细节.
我有两个数学概念,比如环和场.每个领域都是一个环,但不是相反.有几种实施方式中的每个,并且它们共享共同的基类,即AbstractRing
和AbstractField
,从前者衍生后者.现在我尝试实现基于的易于编写的引用语义std::shared_ptr
.所以我的Ring
类包含一个std::shared_ptr<AbstractRing>
持有它的实现,以及一堆转发到它的方法.我想Field
继承写作,Ring
所以我不必重复这些方法.特定于字段的方法只是将指针强制转换为AbstractField
,并且我想静态地执行该操作.我可以保证指针实际上是一个是AbstractField
在施工,但是我担心有人会分配Ring
到Ring&
这实际上是一个Field
,从而打破我的假设不变的有关共享包含指针.
由于在编译时无法检测到对向下类型引用的分配,我建议采用动态解决方案。这是一个不寻常的情况,我通常会反对这样做,但可能需要使用虚拟赋值运算符。
class Ring {
virtual Ring& operator = ( const Ring& ring ) {
/* Do ring assignment stuff. */
return *this;
}
};
class Field {
virtual Ring& operator = ( const Ring& ring ) {
/* Trying to assign a Ring to a Field. */
throw someTypeError();
}
virtual Field& operator = ( const Field& field ) {
/* Allow assignment of complete fields. */
return *this;
}
};
Run Code Online (Sandbox Code Playgroud)
这可能是最明智的方法。
另一种方法可能是为引用创建一个模板类,它可以跟踪这一点并简单地禁止使用基本指针 * 和引用 &。模板化的解决方案正确实现可能比较困难,但允许静态类型检查,从而禁止向下转型。这是一个基本版本,至少对我来说,它使用 GCC 4.8 和 -std=c++11 标志(对于 static_assert)正确地给出了编译错误,其中“noDerivs( b )”是错误的根源。
#include <type_traits>
template<class T>
struct CompleteRef {
T& ref;
template<class S>
CompleteRef( S& ref ) : ref( ref ) {
static_assert( std::is_same<T,S>::value, "Downcasting not allowed" );
}
T& get() const { return ref; }
};
class A { int a; };
class B : public A { int b; };
void noDerivs( CompleteRef<A> a_ref ) {
A& a = a_ref.get();
}
int main() {
A a;
B b;
noDerivs( a );
noDerivs( b );
return 0;
}
Run Code Online (Sandbox Code Playgroud)
如果用户首先创建自己的引用并将其作为参数传递,则该特定模板仍然可能被欺骗。最后,保护用户不做愚蠢的事情是一项无望的努力。有时,您所能做的就是给出合理的警告并提供详细的最佳实践文档。
归档时间: |
|
查看次数: |
457 次 |
最近记录: |