将基类的实例向下转换为派生类

时间:2014-03-18 09:49:51

标签: c++ casting memory-layout

我感兴趣是否安全, DOWNCAST (感谢Mike)在某些条件下派生类的基类实例。我认为样本是最简单的解释方式:

struct BaseA
{
    void foo() const {}     
    double bar_member;
    // no virtuals here
};

struct DerivedA : public BaseA
{
    double bar(double z) {bar_member = z;return bar_member;}
    // DerivedA does not add ANY member variables to BaseA.
    // It also does not introduce ANY virtual functions.
};    

struct BaseB
{
    BaseA baseA;
};

// add extra functionality to B, to do this,
// i also need more functionality on baseA.
struct DerivedB : public BaseB
{
    // is this "safe"? since BaseA and DerivedA
    // should have the same memory layout?!?
    DerivedA& getA() {return *static_cast<DerivedA*>(&baseA);}

    double foo(double z) {return getA().bar(z);} 
};

#include <iostream>

int main(int argc, char** argv)
{
    DerivedB b;
    // compiles and prints expected result
    std::cout << b.foo(argc) << std::endl;
}

在我的例子中,BaseA和BaseB类实现了某种视图概念。但是,它们还包含在派生类中添加更多功能所需的所有数据成员。我知道我可以实现视图只保留对提供功能的类的引用。但是,这会带来一些缺点:

  • 我需要重写视图类的整个界面。
  • 在我的例子中,Derived类拥有一个额外的模板参数(一个回调类型),我想在视图中删除它。因此,视图不能直接引用提供功能的类。

我测试了我的代码,但是,我确实不相信这种方法。是的,我知道我可以用虚拟机等实现其中的一些功能但是非常性能至关重要......

欢迎任何想法,提示

马丁

感兴趣的人: 我通过以下方式改变了我的设计:

struct DerivedB : public BaseB
{
    // encapsule the required extended functionality of BaseA member
    struct OperateOnBaseA
    {
         OperateOnBaseA(BaseA& a);
         double dosomething(double);
    };

    OperateOnBaseA a_extension;

    DerivedB() :a_extension(baseA) {}

    double foo(double z) {return a_extension.dosomething();} 
}; 

3 个答案:

答案 0 :(得分:1)

关于技术方面:当然禁止2011标准,5.2.9.11,静态演员。设B为D的基础:

  

如果类型“指向cv1 B的指针”的prvalue指向实际上是D类型对象的子对象的B,则结果指针指向类型D的封闭对象。否则,强制转换的结果是未定义。

另一方面,如果有人能找到一个不仅仅是这样做的实现,我会感到惊讶,因为类,方法和静态强制转换的明显实现。

答案 1 :(得分:0)

我发现你的行为不够干净。假设有一个“数据源类型”SourceA和一个“数据视图类型”ViewB,我会更像这样:

#include <iostream>

using namespace std;

template<typename T>
class SourceA_base
{
protected:
    T data;
public:
    using value_type = T;
    SourceA_base(T&& a) : data(std::move(a)) { }
    SourceA_base(T const& a) : data() { }
    void foo() const {}
};

template<typename T>
class SourceA : public SourceA_base<T>
{
    using B = SourceA_base<T>;
public:
    using B::B;
    T bar(T z) { return B::data = z; }
};

template<typename U>
class ViewB_base
{
protected:
    U&& source;
public:
    using value_type = typename std::remove_reference<U>::type::value_type;
    ViewB_base(U&& a) : source(std::forward<U>(a)) { }
};

template<typename U>
class ViewB : public ViewB_base<U>
{
    using B = ViewB_base<U>;
    using T = typename B::value_type;
public:
    using B::B;
    T foo(T z) { return B::source.bar(z); }
};

int main ()
{
    using S = SourceA<double>;
    S a{3.14};
    ViewB<S&> b{a};
    std::cout << b.foo(6.28) << std::endl; // compiles and prints expected result
    std::cout << ViewB<S>{S{2}}.foo(4) << std::endl; // still works
}

也就是说,所有(源/视图)类型都是模板化的,视图包含引用,并且没有向下转换。关于使用参考文献的预订:

  • 重写整个界面:现在不需要了,感谢模板。
  • 删除回调类型:首先,类型擦除和性能关键应用程序并不总是好朋友。其次,你最好让回调擦除它自己的底层类型,而不是视图擦除回调的类型。每个班级都应该做自己的工作。或者,不要删除类型并将其作为模板参数。

我使用了rvalue-references,因此整个事情也适用于临时工,如我的第二个例子所示。也许构造函数在这里并不总是完整/正确。例如const引用;我的实际会有完全模板化的构造函数(接受通用引用),但为了使它与单参数隐式定义的复制/移动构造函数合作有点棘手(需要类型特征和enable_if)而我只想高亮显示想法在这里。

您还可以考虑使用元组来保存数据,利用其空基优化。

关于你原来的问题,这个垂头丧气是我永远不会做的事情;对于技术方面,请参阅Peter Schneider的回答。

答案 2 :(得分:0)

您现有的代码具有未定义的行为,如其他答案中所述。如果您不介意一些真正可怕的代码,可以通过在baseA销毁对象并在同一位置创建DerivedA来避免这种情况,因此向下转换是有效的:

#include <new>

struct DerivedB : public BaseB
{
  DerivedB()
  {
    static_assert( sizeof(BaseA) == sizeof(DerivedA), "same size" );
    baseA.~BaseA();
    ::new(&baseA) DerivedA();
  }
  ~DerivedB()
  {
    getA().~DerivedA();
    ::new(&baseA) BaseA();
  }

    DerivedA& getA() {return *static_cast<DerivedA*>(&baseA);}

    double foo(double z) {return getA().bar(z);} 
};

析构函数恢复原始类型的对象,这样当BaseB析构函数销毁其baseA成员时,它会在对象上运行正确类型的析构函数。

但我会避免这样做并重新设计你的课程以另一种方式解决它。