我目前正在研究多态类型和赋值操作之间的相互作用。我主要担心的是,是否有人可能会尝试将基类的值分配给派生类的对象,这会导致问题。
来自 this answer我了解到基类的赋值运算符总是被派生类的隐式定义的赋值运算符隐藏。所以对于简单变量的赋值,不正确的类型会导致编译器错误。但是,如果赋值是通过引用发生的,则情况并非如此:
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
}
这种形式的赋值可能会导致对象状态不一致,但是没有编译器警告,乍一看代码对我来说没有恶意。
是否有任何既定的惯用法来检测此类问题?
我想我只能寄希望于运行时检测,如果我发现这样的无效赋值则抛出异常。刚才我能想到的最好的方法是在基类中自定义赋值运算符,它使用运行时类型信息来确保this
。实际上是指向 base 实例的指针,而不是指向派生类的指针,然后进行逐个成员的手动复制。这听起来像是很多开销,并且严重影响代码的可读性。有没有更简单的?
编辑:由于某些方法的适用性似乎取决于我想做什么,这里有一些细节。
我有两个数学概念,比如 ring和 field .每个字段都是一个环,但反之则不然。每个都有几个实现,它们共享公共(public)基类,即 AbstractRing
和 AbstractField
, 后者源自前者。现在我尝试基于 std::shared_ptr
实现易于编写的引用语义.所以我的 Ring
类包含一个 std::shared_ptr<AbstractRing>
持有它的实现,以及一堆转发给它的方法。我想写Field
作为继承自 Ring
所以我不必重复那些方法。特定于字段的方法将简单地将指针转换为 AbstractField
,并且我想静态地执行该转换。我可以确保指针实际上是一个 AbstractField
在 build 中,但我担心有人会分配 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;
}
};
这可能是最明智的做法。
另一种方法可能是为引用创建一个模板类,它可以跟踪这一点并简单地禁止使用基本指针 * 和引用 &。模板化的解决方案可能更难正确实现,但会允许静态类型检查来禁止向下转换。这是一个基本版本,至少对我而言,它使用 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;
}
如果用户首先创建他自己的引用并将其作为参数传递,这个特定模板仍然可以被愚弄。最后,保护您的用户不做愚蠢的事情是一项无望的努力。有时,您所能做的就是发出合理的警告并提供详细的最佳实践文档。
关于c++ - 检测基类分配给指向派生类的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25547066/