我目前正在调查多态类型和赋值操作之间的相互作用。我主要担心的是有人是否可能尝试将基类的值分配给派生类的对象,这会导致问题。
从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实例的指针,而不是派生的class,然后执行手动逐个成员的副本。这听起来像很多开销,严重影响代码可读性。有更容易的事吗?
编辑:由于某些方法的适用性似乎取决于我想做什么,这里有一些细节。
我有两个数学概念,比如ring和field。每个领域都是一个环,但不是相反。每个都有几个实现,它们共享公共基类,即AbstractRing
和AbstractField
,后者派生自前者。现在我尝试实现基于std::shared_ptr
的易于编写的引用语义。所以我的Ring
类包含一个std::shared_ptr<AbstractRing>
来保存它的实现,还有一堆方法转发给它。我想将Field
写为继承自Ring
,因此我不必重复这些方法。特定于字段的方法只是将指针强制转换为AbstractField
,并且我希望静态地执行该操作。我可以确保指针在构造时实际上是AbstractField
,但是我担心有人会将Ring
分配给Ring&
实际上是Field
{{1}}因此打破了我对所包含的共享指针的假定不变量。
答案 0 :(得分:1)
由于在编译时无法检测到向下转换类型引用,我建议使用动态解决方案。这是一个不寻常的案例,我通常会反对这种情况,但可能需要使用虚拟分配运算符。
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;
}
};
这可能是最明智的做法。
另一种方法是为引用创建一个模板类,可以跟踪它并简单地禁止使用基本指针*和引用&amp ;.模板化解决方案可能更难以正确实现,但允许静态类型检查禁止向下转发。这是一个基本版本,至少对我来说正确地给出了编译错误&#34; noDerivs(b)&#34;使用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;
}
如果用户首先创建自己的引用并将其作为参数传递,则此特定模板仍然可以被欺骗。最后,保护用户不做蠢事是一种无望的努力。有时您所能做的就是给出公平的警告,并提供详细的最佳实践文档。