检测基类的分配以指向派生类的引用

时间:2014-08-28 10:53:52

标签: c++ inheritance reference assignment-operator

我目前正在调查多态类型和赋值操作之间的相互作用。我主要担心的是有人是否可能尝试将基类的值分配给派生类的对象,这会导致问题。

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,然后执行手动逐个成员的副本。这听起来像很多开销,严重影响代码可读性。有更容易的事吗?

编辑:由于某些方法的适用性似乎取决于我想做什么,这里有一些细节。

我有两个数学概念,比如ringfield。每个领域都是一个环,但不是相反。每个都有几个实现,它们共享公共基类,即AbstractRingAbstractField,后者派生自前者。现在我尝试实现基于std::shared_ptr的易于编写的引用语义。所以我的Ring类包含一个std::shared_ptr<AbstractRing>来保存它的实现,还有一堆方法转发给它。我想将Field写为继承自Ring,因此我不必重复这些方法。特定于字段的方法只是将指针强制转换为AbstractField,并且我希望静态地执行该操作。我可以确保指针在构造时实际上是AbstractField,但是我担心有人会将Ring分配给Ring&实际上是Field {{1}}因此打破了我对所包含的共享指针的假定不变量。

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;
}

如果用户首先创建自己的引用并将其作为参数传递,则此特定模板仍然可以被欺骗。最后,保护用户不做蠢事是一种无望的努力。有时您所能做的就是给出公平的警告,并提供详细的最佳实践文档。