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

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实例的指针,而不是派生类的指针,然后执行手动逐个成员的副本.这听起来像很多开销,严重影响代码可读性.有更容易的事吗?

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

我有两个数学概念,比如.每个领域都是一个环,但不是相反.有几种实施方式中的每个,并且它们共享共同的基类,即AbstractRingAbstractField,从前者衍生后者.现在我尝试实现基于的易于编写的引用语义std::shared_ptr.所以我的Ring类包含一个std::shared_ptr<AbstractRing>持有它的实现,以及一堆转发到它的方法.我想Field继承写作,Ring所以我不必重复这些方法.特定于字段的方法只是将指针强制转换为AbstractField,并且我想静态地执行该操作.我可以保证指针实际上是一个是AbstractField在施工,但是我担心有人会分配RingRing&这实际上是一个Field,从而打破我的假设不变的有关共享包含指针.

Zoo*_*tor 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;
    }
};
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)

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