检测基类对指向派生类的引用的赋值
Detect assignment of base class to reference pointing at derived class
我目前正在研究多态类型和赋值操作之间的相互作用。我主要关心的是,是否有人试图将基类的值分配给派生类的对象,这会导致问题。
从这个答案中,我了解到基类的赋值运算符总是被派生类的隐含定义的赋值运算符所隐藏。因此,对于简单变量的赋值,不正确的类型会导致编译器错误。然而,如果通过引用进行分配,则情况并非如此:
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
实际上是指向基类实例的指针,而不是指向派生类的指针,然后逐个成员手动复制。这听起来开销很大,严重影响了代码的可读性。有什么更容易的吗?
编辑:由于某些方法的适用性似乎取决于我想做什么,下面是一些细节。
我有两个数学概念,比如环和场。每个域都是一个环,但不是相反。每个都有几个实现,并且它们共享公共基类,即AbstractRing
和AbstractField
,后者源自前者。现在,我尝试实现基于std::shared_ptr
的易于编写的引用语义。因此,我的Ring
类包含一个std::shared_ptr<AbstractRing>
来保存它的实现,以及一堆转发给它的方法。我想把Field
写成从Ring
继承的,这样我就不必重复那些方法了。特定于字段的方法只需将指针强制转换为AbstractField
,我希望静态地执行这种强制转换。我可以确保指针实际上是构造中的AbstractField
,但我担心有人会将Ring
分配给实际上是Field
的Ring&
,从而打破我对包含的共享指针的假定不变量。
由于在编译时无法检测到向下转换类型引用的赋值,我建议使用动态解决方案。这是一种不寻常的情况,我通常会反对,但可能需要使用虚拟赋值运算符。
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;
}
};
这可能是最明智的做法。
另一种选择可能是为引用创建一个模板类,它可以跟踪这一点,并简单地禁止使用基本指针*和引用&。模板化的解决方案可能更难正确实现,但会允许静态类型检查,从而禁止向下转换。这是一个基本版本,至少对我来说,它正确地给出了一个编译错误,"noDerivs(b)"是错误的根源,使用GCC 4.8和-std=c++11标志(用于static_assert)。
#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;
}
如果用户首先创建自己的引用并将其作为参数传递,那么这个特定的模板仍然可能被愚弄。最后,保护用户不做愚蠢的事情是一种无望的努力。有时,你所能做的就是给出一个公平的警告,并提供一份详细的最佳实践文档。
- 标准库类型的赋值运算符的引用限定符
- 移动赋值运算符;尝试引用已删除的函数.我该如何解决这个问题?
- 为什么我需要三个嵌套的大括号来调用赋值运算符,将const引用到二维数组
- 复制引用结构上的赋值
- 作为赋值(增加引用变量)C++的左操作数所需的左值
- 如果类在 C++ 中具有常量或引用类型的非静态数据成员,为什么编译器不提供默认赋值运算符?
- 返回 T 引用的 Const 函子禁止赋值
- 为什么在C++中,内建赋值返回的是非常量引用而不是常量引用
- 使用变量参数列表为传递的引用赋值(VS2010中出错)
- 在 C++ 中通过引用返回 - 引用赋值与值赋值
- 为什么不能为非常量引用赋值
- c++ 取消引用赋值是按位赋值还是按位赋值或"smart"赋值?
- 类成员作为对另一个的引用:赋值运算符中的崩溃
- c++的引用赋值相当于PHP
- Std::swap只读引用赋值错误
- 通过引用赋值对象,而不复制
- 对引用赋值的后增量
- c++指针解引用赋值
- c++构造函数初始化引用赋值
- 将一个glm::vec3常量引用赋值给另一个glm::vec3时奇怪的崩溃